Advertisement
Scroll to top
Read Time: 13 min

() translation by (you can also view the original English article)

Погляньте на будь-яку сучасну веб-сторінку і ви помітите, що в ній незмінно присутній контент, зібраний докупи з різноманітних ресурсів; вона може містити віджети для шарінгу (публікації) контенту у соціальних мережах від Twitter або Facebook або віджет відеоплеєра від Youtube, вона може надавати персоналізовану рекламу з деяких рекламних серверів (* веб-сервер, що зберігає рекламну інформацію, яка використовується в онлайновому маркетингу, і доставляє її на різноманітні цифрові платформи. Тут і надалі примітка перекладача) або містити деякі допоміжні скрипти або стилі зі сторонніх бібліотек, що хостяться на CDN (* content delivery network – мережа доставки контенту) тощо. І якщо у вебі все базується на HTML (чому віддається перевага сьогодні), то існує висока ймовірність виникнення конфліктних ситуацій між розміткою, скриптами та стилями, що доставляються з різних джерел. Звичайно для попередження подібних ситуацій використовуються простори імен (* набір правил іменування, що регулює видимість об'єктів у програмі або хост-комп'ютерів у комп'ютерній мережі. Простір імен може бути плоским (flat namespace) та ієрархічним (hierarchical namespace). Передбачено, що всі імена у просторі імен мають бути унікальними), завдяки чому проблема почасти вирішується, проте при цьому не забезпечується інкапсуляція (* в ООП - приховування внутрішньої структури даних і реалізації методів об'єкта від програми таким чином, щоб інші об'єкти не потребували знань про його внутрішню структуру. Доступний тільки інтерфейс об'єкта, через який здійснюється уся взаємодія з ним).

Інкапсуляція – один з китів, на які спирається парадигма об'єктно-орієнтованого програмування, і звичайно використовується для обмеження внутрішнього представлення об'єкта від зовнішнього світу.

Повертаючись до нашої проблеми, скажу, що ми безумовно можемо інкапсулювати код JavaScript за допомогою замикань або патерну модуль, проте чи можемо ми застосувати ций же підхід і до розмітки HTML. Уявіть, що нам потрібно створити віджет UI (* користувальницький інтерфейс); чи можемо ми приховати деталі реалізації нашого віджета від коду JavaScript та CSS, підключеного на сторінці, що користується нашим віджетом? Або навпаки, чи можемо ми попередити порушення роботи нашого віджета або порушення його зовнішнього вигляду через вплив коду, що використовує наш віджет.


Вирішення проблеми за допомогою Shadow DOM

Єдине рішення, при якому створюється границя між написаним вами кодом та кодом, що його використовує, жахливе і полягає у використанні громіздкого елементу iFrame, велика кількість атрибутів якого вважається застарілою (* є дочірнім контекстом для браузера, завдяки якому до поточної сторінки встоюється інша сторінка HTML). Так чи повинні ми завжди пристосовуватися до цього підходу?

Вже ні! Shadow DOM надає нам елегантний спосіб перекривання звичайного піддерева DOM спеціальним фрагментом документа (* інтерфейс DocumentFragment являє собою мінімальний об'єкт документа, у якого нема батька. Використовується для зберігання сегмента структури документа, що складається з вузлів. Оскільки він не є частиною деревоподібної структури активного документа, то зміни, внесені во фрагмент, не впливають на документ), що містить інше дерево вузлів, недоступних для скриптів та стилів. Цікаво те, що це зовсім не новинка! Різноманітні браузери вже використовували цю методологію для реалізації нативних віджетів на зразок date (* віджет для вибору часу), sliders (* слайдер), audio (* аудіоплеєр), video player (* відеоплеєр) тощо.

Активуємо показ Shadow DOM

На момент написання цього посібника поточна версія Chrome (v29) підтримує інспектування Shadow DOM за допомогою Chrome DevTools. Відкрийте Devtools та натисніть кнопку з іконкою зубчиків шестірні зправа внизу екрана для вікриття панелі Settings, прокрутіть повзунок трохи донизу та побачите флажок для активації показу Shadow DOM.

Turn on Shadow DOMTurn on Shadow DOMTurn on Shadow DOM

Тепер, коли ми активували його в нашому браузері, давайте ознайомимося з внутрішньою будовою аудіоплеєра за налаштуванням. Просто введіть:

1
<audio width="300" height="32" src="http://developer.mozilla.org/@api/deki/files/2926/=AudioTest_(1).ogg" autoplay="autoplay" controls="controls">
2
 Your browser does not support the HTML5 Audio.
3
 </audio>

до вашої HTML-розмітки. Завдяки ньому показується наступний нативний аудіоплеєр у браузерах, що його підтримують:

audio_player

Тепер давайте, проінспектуйте цей віджет аудіоплеєра, що тільки-но створили.

Shadow DOM of Native Date WidgetShadow DOM of Native Date WidgetShadow DOM of Native Date Widget

Вау! В інспекторі показіється внутрішня організація аудіоплеєра, яку без активації показу Shadow DOM було приховано. Як ми бачимо в елементі audio використовується фрагмент документа для розміщення внутрішнього контенту віджета та його додавання до елемента-контейнера (відомий як Shadow Host).

Shadow Host та Shadow Root

  • Shadow Host: елемент DOM, в якому розташовується піддерево Shadow DOM, або вузол DOM, у якому розташовується Shadow Root.
  • Shadow Root: корінь піддерева DOM, в якому розташовуються вузли DOM. Це спеціальний вузол, за допомогою якого створюється границя між звичайними вузлами DOM та вузлами Shadow DOM. Саме ця границя дозволяє інкапсулювати вузли Shadow DOM від будь-якого коду JavaScript або CSS на сторінці, що його використовує.
  • Shadow DOM: дозволяє об'єднати велику кількість піддерев DOM в одне велике дерево. На наступному зображенні з робочого проекту W3C відмінно проілюстровано концепцію перекривання вузлів. Тут показано, як вони виглядають до додавання контенту Shadow Root в елемент Shadow Host:
    Normal Document Tree Shadow DOM SubtreesNormal Document Tree Shadow DOM SubtreesNormal Document Tree Shadow DOM Subtrees

    Після виконання коду для додавання Shadow DOM дерево Shadow замінює контент Shadow Host.

    Composition CompleteComposition CompleteComposition Complete

    Цей процес перекривання вузлів часто називають композицією (* в ООП – метод створення нового об'єкта шляхом об'єднання старих та нових частин, у протилежність наслідуванню).

  • Shadow Boundary (* границя): відмічено пунктирною лінією на зображенні вище. За допомогою неї відмічається границя між звичайним світом DOM та світом Shadow DOM. Скрипти з обох сторін не можуть перетнути цю границю та порушити роботу коду на іншій стороні.

Hello Shadow DOM World (* Hello World – перша найпростіша програма, яку створюють початківці в області комп'ютерних наук)

Досить балакати, давайте візьмемося за написання деякого коду. Припустимо, що ми маємо наступну розмітку, за допомогою якої показується просте повідомлення з привітанням.

1
<div id="welcomeMessage">Welcome to My World</div>

Додайте наступний код JavaScript або скористатйеся Fiddle (* (* фрагмент коду HTML, CSS або JavaScript, що розміщується в онлайн товариств JSFiddle):

1
var shadowHost = document.querySelector("#welcomeMessage");
2
var shadowRoot = shadowHost.webkitCreateShadowRoot();
3
shadowRoot.textContent = "Hello Shadow DOM World";

Тут ми створюємо Shadow Root за допомогою функції webkitCreateShadowRoot(), додаємо його до Shadow Host та потім просто замінюємо контент.

Зверніть увагу на префікс, специфічний для певного виробника браузерів, - webkit перед іменем функції. Він вкзаує на те, що ця функціональна можливість (* Shadow DOM) зараз підтримується тільки у деяких браузерах, що працюють на основі webkit (* движок браузера).

Якщо би ви виконали код цього прикладу у браузері з підтримкою Shadow DOM, то побачили би "Hello Shadow DOM World" замість "Welcome to My World", оскільки вузли Shadow DOM перекрили би звичайні.

Застереження: Як деякі з вас могли помітити, ми змішуємо розмітку з кодом скрипта, що звичайно не рекомендіється, і Shadow DOM не є виключенням. Ми навмисно не використовували шаблони зараз (* дозволяє вам об'являти фрагменти DOM, що піддаються парсингу, неактивні при завантаженні строінки та можуть бути активовані пізніше при виконанні коду), щоб уникнути плутанини. Shadow DOM також надає нам рішення цієї проблеми, і ми розглянемо його дуже скоро.


Деякі відомості відносно Shadow Boundary

Якщо ви спробуєте звернутися до контенту відображуваного дерева за допомогою JavaScript наступний чином:

1
var shadowHost = document.querySelector("#welcomeMessage");
2
var shadowRoot = shadowHost.webkitCreateShadowRoot();
3
shadowRoot.textContent = "Hello Shadow DOM World";
4
5
console.log(shadowHost.textContent);
6
 // Prints "Welcome to My World" as the shadow DOM nodes are encapsulated and cannot be accessed by JavaScript

то отримаєте початковий контент "Welcome to My World", а не контент, що власне відображується на сторінці, оскільки дерево Shadow DOM інкапсульовано від будь-яких скриптів.  Це також означає, що віджет, який ви створите за допомогою Shadow DOM, захищено від впливу будь-яких небажаних/несумісних скриптів, вже присутніх на сторінці.

Інкапсуляція стилів

Подібним чином, перетинання Shadow Boundary будь-яким селектором CSS заборонено. Ознайомтеся з наступним кодом, у якому ми застосували до елементів списку червоний колір, проте цей стиль застосовується тільки до вузлів, що є частиною батьківської сторінки, і на елементи списку, які є частиною Shadow Root, він не впливає.

1
<div class="outer">
2
  <div id="welcomeMessage">Welcome to My World</div>
3
  <div class="normalTree">Sample List
4
  <ul>
5
      <li>Item 1</li>
6
      <li>Item 2</li>
7
  </ul>
8
  </div>
9
</div>
10
<style>
11
   div.outer li {  
12
      color: red;  
13
   } 
14
   div.outer{  
15
      border: solid 1px;  padding: 1em; 
16
   }
17
</style>
18
<script type="text/javascript">
19
    var shadowHost = document.querySelector("#welcomeMessage");
20
    var shadowRoot = shadowHost.webkitCreateShadowRoot();
21
    shadowRoot.innerHTML = ["<div class='shadowChild'>",
22
                            "Shadow DOM offers us Encapsulation from",
23
                            "<ul>",
24
                            "<li>Scripts</li>",
25
                            "<li>Styles</li>",
26
                            "</ul>",
27
                            "</div>"
28
                            ].join(',').replace(/,/g,"");
29
</script>

Ви можете побачити код у дії на Fiddle. Подібна інкапсуляція спрацьовує навіть якщо ми змінюємо напрям обходу дерева (* при застосуванні стилів). Будь-які стильові правила, визначені усередині Shadow DOM, не впливають на батьківський документ та залишаються тільки в області видимості Shadow Root. Ознайомтеся з цим Fiddle у якості прикладу, де ми застосовуємо синій колір до елементів списку у Shadow DOM, проте на елементи списку батьківського документа цей стиль не впливає.

Але при цьому є одне примітне виключення; Shadow DOM надає нам можливість задати стильве оформлення для Shadow Host, вузла DOM, в якому розташовується Shadow DOM. В ідеалі цей вузол розташовується за межами Shadow Boundary і не є частиною Shadow Root, проте завдяки використанню правила @host ми можемо додати стильове оформлення до Shadow Host так, як ми зробили для повідомлення з привітанням нижче.

1
<div id="welcomeMessage">Welcome to My World</div>
2
<script type="text/javascript">
3
  var shadowHost = document.querySelector("#welcomeMessage");
4
  var shadowRoot = shadowHost.webkitCreateShadowRoot();
5
  shadowRoot.innerHTML = ["<style>",
6
                          "@host{ ",
7
                             "#welcomeMessage{ ",
8
                                "font-size: 28px;",
9
                                "font-family:cursive;",
10
                                "font-weight:bold;",
11
                             "}",
12
                          "}",
13
                          "</style>",
14
                          "<content select=''></content>"
15
                          ].join(',').replace(/,/g,"");
16
</script>

Ознайомтеся з цим Fiddle, де ми додали стильове оформлення для повідомлення з привітанням у Shadow Host за допомогою стильових правил, заданих у Shadow DOM.

Створення зачіпок (* місце у програмі, куди можна підключити додатковий код (звичайно для розширення її функціональних можливостей)) для додавання стильового оформлення

Як розробник віджета я міг би захотіти, щоб у користувача мого віджета була можливість додати власне стильове оформлення для певних елементів. Це реалізуємо завдяки додаванню дірки до Shadow Boundary за допомогою користувальницьких псевдо-елементів (* елемент DOM усередині Shadow Root користувальницьокого елемента, для якого автор явно додав можливість задання правил стильового оформлення із-за меж Shadow Root за допомогою псевдо-селекторів). Це подібно тому, як деякі браузери надають розробникам зачіпки для додавання стильового оформлення до деяких внутрішніх елементів нативного віджета. Наприклад для того щоб додати стильове оформлення для повзунка та лінійки нативного слайдера, ви можете використовувати ::-webkit-slider-thumb та  ::webkit-slider-runnable-track наступним чином:

1
input[type=range]{
2
    -webkit-appearance:none;
3
 }
4
 input[type=range]::-webkit-slider-thumb {
5
    -webkit-appearance:none;
6
    height:12px;
7
    width:12px;
8
    border-radius:6px;
9
    background:yellow;
10
    position:relative;
11
    top:-5px;
12
 }
13
 input[type=range]::-webkit-slider-runnable-track {
14
    background:red;
15
    height:2px;
16
 }

Форкніть (* створіть власну копію) цього Fiddle та додайте власне стильове оформлення до нього.

Переорієнтація події

Якщо подія, яка виникає на одному з вузлів Shadow DOM, перетинає Shadow Boundary, то вона переорієнтується таким чином, щоб відноситися до Shadow Host для зберігання інкапсуляції. Розглянемо наступний код:

1
<input id="normalText" type="text" value="Normal DOM Text Node" />
2
<div id="shadowHost"></div>
3
<input id="shadowText" type="text" value="Shadow DOM Node" />
4
<script type="text/javascript">
5
    var shadowHost = document.querySelector('#shadowHost');
6
    var shadowRoot = shadowHost.webkitCreateShadowRoot();
7
    var template = document.querySelector('template');
8
    shadowRoot.appendChild(template.content.cloneNode(true));
9
    template.remove();
10
    document.addEventListener('click', function(e) { 
11
                                 console.log(e.target.id + ' clicked!'); 
12
                              });
13
</script>

Завдяки ньому відображуються два поля для вооду тексту: одне завдяки DOM та інше завдяки Shadow DOM, і потім встановлюється обробник події click для об'єкта document. Тепер, при натисненні по другому полю для вводу тексту, подія виникає всередині Shadow DOM, і при її перетинанні через Shadow Boundary він змінюється так, щоб цільовий елемент змінився на елемент <div> Shadow Host замість поля для вооду тексту <input>. Також ми тут маємо новий елемент <template>; за своєю концепцією він подібний рішенням для створення шаблонів для клієнтської сторони на зразок Handlebars та Underscore, проте не такий просунутий та недостатньо підтримується деякими браузерами. Тим не менше, використання шаблонів для написання Shadow DOM – ідеальний спосіб у порівнянні із застосуванням тегів у скрипті, як ми робили до цього часу у цьому посібнику.


Розділяємо те, про що варто буде потурбуватися

Ми вже знаємо, що завжди варто розділяти власне контент та його представлення; у Shadow DOM не повинно бути ніякого контенту, який потрібно у підсумку показати користувачеві. Замість цього контент повинен завжди розташовуватися на початковій сторінці, а не приховуватися у шаблоні Shadow DOM. При виконанні композиції цей контент потім повинно бути спроектовано до відповідної точки вставки (* місце у документі, де будуть виконуватися деякі дії), яку вказано у шаблоні Shadow DOM. Давайте перепишемо наш приклад «Hello World», пам'ятаючи про вишезгаданий поділ; з робочим прикладом можете ознайомитися на Fiddle.

1
<div id="welcomeMessage">Welcome to Shadow DOM World</div>
2
<script type="text/javascript">
3
    var shadowRoot = document.querySelector("#welcomeMessage").webkitCreateShadowRoot();
4
    var template = document.querySelector("template");
5
    shadowRoot.appendChild(template.content); 
6
    template.remove();
7
</script>

Після відображення сторінки контент Shadow Host проєктується у місце, де з'являється елемент <content> (* раніше використовувався у Shadow DOM в якості точки вставки. Не передбачалося, що він буде використовуватися у звичайному HTML. Його замінено елементом slot>, що використовується для створення точки у DOM, куди може бути вставлено Shadow DOM). Це дуже спрощенний приклад, у якому до <content> при композиції переноситься все, що знаходиться у Shadow Host. Проте до нього також без проблем може переноситися і вибірковий контент з Shadow Host за допомогою атрибута select, як показано нижче:

1
<div id="outer">How about some cool demo, eh ?
2
    <div class="cursiveButton">My Awesome Button</div>
3
</div>
4
<button>
5
  Fallback Content
6
</button>
7
<style>
8
button{ 
9
   font-family: cursive;  
10
   font-size: 24px;
11
   color: red; 
12
}
13
</style>
14
<script type="text/javascript">
15
    var shadowRoot = document.querySelector("#outer").webkitCreateShadowRoot(); 
16
    var template = document.querySelector("template"); 
17
    shadowRoot.appendChild(template.content.cloneNode(true));
18
    template.remove();
19
</script>

Ознайомтеся з робочим прикладом та поекспериментуйте з ним, щоб краще зрозуміти концепцію точок вставки та проектування.


Web Components

Як ви вже, скоріш за все, знаєте, – Shadow DOM – частина специфікації Web Components, що пропонує й інші елегантні інструменти на зразок:

  1. Templates – використовується для розміщення неактивної розмітки, яку потрібно буде використовувати пізніше. Під неактивністю мається на увазі, що всі зображення у розмітці не завантажуються і підключені скрипти не виконуються до тих пір, поки контент шаблону не становиться власне частиною сторінки.
  2. Decorators – використовуються для застосування шаблонів на основі селекторів CSS, і, таким чином, можна вважати, що вони прикрашають існуючі елементи завдяки покращенню їх представлення.
  3. HTML Imports – надає нам можливість повторного використання інших документів HTML у нашому документі без потреби явного виконання запитів XHR (* XMLHttpRequest) та написання обробників подій для нього.
  4. Custom Elements (* користувальницькі елементи) – дозволяють нам визначати нові типи елементів HTML, що потім можна об'являти у розмітці. Наприклад, якщо ви хочете створити свій власний віджет для навігації, ви визначаєте ваш елемент для навігації, наслідуючи властивості та методи від HTMLElement та надаючи певні функції зворотного виклику, потрібні для реалізації його життєвого цикла, за допомогою яких задається поведінка при виникненні певних подій (construction, change, destruction) для виджета, і просто використовуєте цей віджет у вашій розмітці у вигляді <myAwesomeNavigation attr1="value1"..></myAwesomeNavigation>. Таким чином, користувальницькі елементи по суті надають нам спосіб об'єднати всі можливості Shadow DOM, приховуючи внутрішні деталі та об'єднуючи все докупи.

Я не буду довго балакати про інші аспекти специфікації Web Components у цьому посібнику, проте добре, якщо ми будемо пам'ятати, що разом вони дозволяють нам створювати віджети UI, які можна повторно використовувати та які виглядають однаково у різних браузерах, а також повністю інкапсульовані від усіх скриптів та стилів сторінки, що їх використовує.


Завершення

Специфікація Web Components – проект, що розвивається, і навелений код прикладів, що працює сьогодні, може не працювати у наступний версіях. Наприклад раніше у посібниках на цю тему використовувався метод webkitShadowRoot(), що більше на спрацьовує; замість нього використовується createWebkitShadowRoot() для створення Shadow Root. Так що, якщо ви хочете скористатися цим посібником для створення деяких крутих демоверсій за допомогою Shadow DOM, то завжди краще звернутися до специфікації за подробицями.

Зараз тільки Chrome та Opera підтримують цю технологію, так що я був би насторожі при додаванні будь-якого Shadow DOM до моїх кінцевих робіт, проте враховуючи те, що Google надає Polymer, який працює на основі Web Components, і Polyfills (* polyfill – бібліотека, що додає до старих браузерів підтримку можливостей, які вбудовано до сучасних браузерів), призначений для нативної підтримки Shadow DOM браузерами, цю технологію повинен засвоїти кожен веб-розробник.

Ви також можете слідкувати за подіями, що відбуваються у світі Shadow DOM, підписавшися на канал Google+ Channel. Також ознайомтеся з інструментом Shadow DOM Visualizer (* візуалізатор роботи Shadow DOM), що допомагає вам візуалізувати те, як Shadow DOM відображається у браузері.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.