Первая часть обзора веб-компонентов посвящена стандарту Custom Elements.
Теневой DOM
Спецификации Shadow DOM
привносят концепцию области в определения веб-стилей. Эти области реализуются через несколько DOM
деревьев, встроенных в один документ. В некотором смысле подход Shadow DOM
можно сравнить с элементом iframe
внутри документа, поскольку он имеет естественно изолированную структуру DOM
. Что касается документов, определения Shadow DOM
распределены между несколькими различными стандартами, такими как DOM specification
, HTML specification
, CSS Scoping Module
, UI Events
и т. Д.
DOM specification
определяет модель для деревьев узлов и то, как узлы взаимодействуют друг с другом посредством событий. Дерево узлов DOM
- это иерархическая структура узлов, в которой одни узлы могут разветвляться, а другие - просто листья. Спецификация различает Document Tree
и Shadow Tree
через подключение к их корням. Объект document
является корнем Document Tree
, в то время как для Shadow Tree
предлагается специальный тип корня, который называется Shadow Root
. Shadow Root
должен быть прикреплен к любому другому дереву под элементом host
. Shadow Root
можно рассматривать как Document Fragment
- спецификация определяет отношения принадлежности между двумя интерфейсами.
<script> class CustomElement extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }) } connectedCallback() { this.shadowRoot.innerHTML = '<a href="#">My link</a>' } } customElements.define('hello-shadow-dom', CustomElement) </script> <hello-shadow-dom></hello-shadow-dom>
Единственный способ создать Shadow DOM
- вызвать метод attachShadow()
класса Element
. Он создает теневой корень для элемента. К элементу можно прикрепить только один теневой корень. Свойство mode
, указанное в attachShadow()
call, определяет, доступно ли дерево из JavaScript
или нет. Альтернативой «открытому» значению может быть «закрытое» значение. Присоединенное свойство shadowRoot
является родительским для указанного дерева узлов. shadowRoot.host
настроен на элемент hello-shadow-dom
в следующем примере.
<hello-shadow-dom></hello-shadow-dom>
#shadow-root (open)
<a href="#">My link</a>
Функция Slot
широко используется в UI Libraries
. В Angular,
это называется проекцией или включением и поддерживается директивой ng-content
. Vue
кажется более совместимым со стандартами - он вызывает те же функции, что и slot
. Наконец, в React
эта способность еще более актуальна для библиотеки и может быть достигнута {props.children}
использованием внутри контейнера элементов. Функция Slot
служит заполнителю. С одной стороны, Shadow Tree
может содержать slots
элементы, определенные с помощью тега slot
. С другой стороны, когда разработчик хочет использовать элемент, он или она может указать разметку для объявленного slots
, чтобы содержимое отображалось внутри Shadow Tree
оболочки.
<script> class OhMySlotElement extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }) } connectedCallback() { this.shadowRoot.innerHTML = `<p> <slot name="my-text">default</slot> </p>` } } customElements.define('oh-my-slot', OhMySlotElement); </script> <oh-my-slot> <pre slot="my-text"> anything </pre> </oh-my-slot>
Последнее дерево DOM
содержит как Shadow Root
с дочерним элементом p
, так и элемент pre
разработчика. Последний связан с узлом slot
и заменяет его содержимое по умолчанию. Атрибут name в теге slot
определяет, какой именно заполнитель использовать. Также можно использовать безымянный слот, если нет доступных альтернатив.
Спецификация Shadow DOM
позволяет разработчику объявлять изолированные стили и предоставляет API для использования основного контекста документа. Элементы внутри Shadow Tree
недоступны для внешних селекторов CSS
документа.
В следующем примере представлены два раздела стилей: один находится внутри основного документа, другой - внутри прикрепленного элемента Shadow DOM
.
<style>h3 { color: blue; }</style> <script> class CustomElement extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }) } connectedCallback() { this.shadowRoot.innerHTML = ` <style>h3 { color: red; }</style> <h3>Shadow DOM</h3> ` } } customElements.define('my-element', CustomElement); </script> <h3>Normal DOM</h3> <my-element></my-element>
Стиль, примененный к элементу h3
основного документа, имеет синий цвет, а h3
внутри my-element
окрашен в красный цвет. Примечательно, что основной документ может определять стили для самого host
элемента. Если для стиля my-element
задан зеленый цвет, он будет применен к его дочерним элементам, как в обычной ситуации наследования стилей. Унаследованный цвет по-прежнему имеет меньший приоритет, чем любой другой определенный стиль.
Селектор псевдокласса :host
смотрит в элемент host
Shadow Root
.
:host { color: green; }
Стили в основном документе имеют больший приоритет по сравнению со стилями :host
внутри элемента. Можно использовать функцию :host()
для условных селекторов
:host(.error) { color: red; }
Когда к host
присоединен класс error
, к нему будет применен красный цвет.
Другой функциональный селектор - :host-context()
, который может определить, есть ли предок, соответствующий указанному селектору за пределами Shadow Tree
.
Точно так же функция ::slotted()
может применяться к «внешним» элементам внутри slots
.
Еще есть возможность настраивать стили в основном документе. Это функция CSS custom properties
или переменных, примененная к Shadow DOM
. Он основан на нескольких принципах:
- Свойства определены и используются внутри
Shadow DOM
. Таким образом, автор компонента несет ответственность за предоставление API (или списка свойств, которые могут быть перезаписаны извне). - Переменные применяются к элементу
host
в основном документе. Как сказано в спецификации, настраиваемые свойства наследуются. Это означает, что если он не определен на уровне дочернего элемента, он принимает значение родительского элемента.
Когда пользовательский элемент использует Shadow DOM
со стилем, определенным как:
h3 { color: var(--my-color); }
Он попытается получить доступ к переменной --my-color
. Таким образом, разработчик, использующий элемент, может добавить переменную в свои определения стиля.
custom-element { --my-color: red; }
Однако переменная --my-color
не определена в селекторе h3
ни в одном определении стиля, она наследуется от host
Shadow Root
и применяется к DOM
внутреннего элемента. Значение цвета h3
красный.
Разумно использовать резервное значение или значение по умолчанию для этих публично определенных переменных. Если покупатель не указывает переменную, она все равно имеет разумное значение.
Подводя итог, Shadow DOM
предлагает следующие функции:
- Изолированный
DOM
, отделенный от документа. - Составление элемента с частным или публичным доступом к дереву.
slots
с целью повторного использования.CSS
имеет ограниченную область видимости, поэтому стили страницы не смешиваются со стилями, объявленными внутриShadow Tree
. Такой подход упрощает определение стилей и снижает потребность в иерархических селекторах.
HTML-шаблон
Спецификация HTML Template
обычно сочетается с Shadow DOM
и Custom Elements
. В нем объясняется, как указать макет, не влияя на сам документ. Любое содержимое внутри тега template
не отображается при загрузке страницы и может использоваться позже кодом JavaScript
для создания экземпляров элементов.
<template id="mytemplate"> <script> alert() </script> </template>
В данном примере браузер теперь покажет всплывающее окно; однако это указано внутри тега script
.
const template = document.querySelector('#mytemplate') const clone = document.importNode(template.content, true) document.body.appendChild(clone)
Всплывающее окно появляется только тогда, когда шаблон создан и добавлен в документ. content
- это еще один Document Fragment
, который содержит указанную структуру HTML
. Метод документа importNode()
генерирует копию данного узла. Последний аргумент, который он принимает, - это «глубокий» флаг. По умолчанию это false
, и будет скопирован только элемент высокого уровня, поэтому для копирования всего макета следует использовать true
.
Имеет смысл создать template
элементов, содержащих внутри style
определение.
<template id="mytemplate"> <style> :host { color: red } </style> </template>
Определение соответствует естественному синтаксису HTML
, не отображается при загрузке страницы и может использоваться позже, когда разработчику необходимо добавить экземпляр шаблона.
const template = document.querySelector('#mytemplate') const div = document.createElement('div') const root = div.attachShadow({ mode: 'open' }) const clone = document.importNode(template.content, true) root.appendChild(clone) document.body.appendChild(div)
Резюме
Спецификации Custom Elements
, Shadow DOM
и HTML Template
позволяют разработчику создавать повторно используемые компоненты с инкапсулированными DOM
стилями и поведением. Большинство заявленных API уже доступны в основных браузерах и имеют полифилы, имитирующие остальные.
Удачного кодирования и надеемся увидеть вас в комментариях!