[Перевод] Как работает JS: технология Shadow DOM и веб-компоненты
[Советуем почитать] Предыдущие 16 частей цикла
Сегодня, в переводе 17 части материалов, посвящённых особенностям всего, что так или иначе связано с JavaScript, речь пойдёт о веб-компонентах и о различных стандартах, которые направлены на работу с ними. Особое внимание здесь будет уделено технологии Shadow DOM.
Обзор
Веб-компоненты — это семейство API, предназначенных для описания новых элементов DOM, подходящих для повторного использования. Функционал таких элементов отделён от остального кода, их можно применять в веб-приложениях собственной разработки.
Существует четыре технологии, относящиеся к веб-компонентам:
- Shadow DOM (теневой DOM)
- HTML Templates (HTML-шаблоны)
- Custom Elements (пользовательские элементы)
- HTML Imports (HTML-импорт)
В этом материале мы поговорим о технологии Shadow DOM, которая разработана для создания приложений, основанных на компонентах. Она предлагает способы решения распространённых проблем веб-разработки, с которыми вы, возможно, уже сталкивались:
- Изоляция DOM: компонент обладает изолированным деревом DOM (это означает, что команда
document.querySelector()
не позволит обратиться к узлу в теневом DOM компонента). Кроме того, это упрощает систему CSS-селекторов в веб-приложениях, так как компоненты DOM изолированы, что даёт разработчику возможность использовать одни и те же универсальные идентификаторы и имена классов в различных компонентах, не беспокоясь о возможных конфликтах имён. - Изоляция CSS: CSS-правила, описанные внутри теневого DOM, ограничены им. Эти стили не покидают пределов элемента, они не смешиваются с другими стилями страницы.
- Композиция: разработка декларативного API для компонентов, основанного на разметке.
Технология Shadow DOM
Тут предполагается, что вы уже знакомы с концепцией DOM и с соответствующими API. Если это не так — можете почитать этот материал.
Shadow DOM — это, в целом, то же самое, что и обычный DOM, но с двумя отличиями:
- Первое заключается в том, как Shadow DOM создают и используют, в частности, речь идёт об отношении Shadow DOM к остальным частям страницы.
- Второе заключается в поведении Shadow DOM по отношению к странице.
При работе с DOM создаются узлы DOM, которые присоединяются, в качестве дочерних элементов, к другим элементам страницы. В случае с технологией Shadow DOM создают изолированное дерево DOM, которое присоединяется к элементу, но оно отделено от его обычных дочерних элементов.
Это изолированное поддерево называют shadow tree (теневое дерево). Элемент, к которому присоединено такое дерево, называется shadow host (теневой хост-элемент). Всё, что добавляется в теневое поддерево DOM, оказывается локальным для элемента, к которому оно присоединено, в том числе — стили, описываемые с помощью тегов . Именно так в рамках технологии Shadow DOM обеспечивается изоляция CSS.
Создание Shadow DOM
Shadow root (теневой корневой элемент) — это фрагмент документа, который присоединяется к хост-элементу. Элемент обзаводится теневым DOM тогда, когда к нему присоединяют теневой корневой элемент. Для того, чтобы создать для некоего элемента теневой DOM, нужно воспользоваться командой вида element.attachShadow()
:
var header = document.createElement('header');
var shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.appendChild(document.createElement(' Shadow DOM
');
Надо отметить, что в спецификации Shadow DOM имеется список элементов, к которым нельзя подключать теневые поддеревья DOM.
Композиция в Shadow DOM
Композиция определяет возможности элементов, таких, как Например, элемент Рассмотрим некоторые возможности Shadow DOM применяемые при композиции элементов.
Теневой DOM в этом примере — это компонент
Paragraph content.
Шаблоны полезны и сами по себе, но в полной мере их возможности раскрываются при использовании с пользовательскими элементами. Пользовательские элементы — это тема для отдельного материала, а сейчас, для понимания происходящего, достаточно учитывать то, что API браузеров Определим веб-компонент, который использует наш шаблон в качестве содержимого для своего теневого DOM. Назовём этот новый элемент
Так как мы присоединяем содержимое шаблона к теневому DOM, мы можем включить в шаблон некую информацию о стилизации, в элементе
Paragraph content.
Слоты можно воспринимать как местозаполнители, которые позволяют включать в шаблон собственный HTML-код. Это позволяет создавать универсальные HTML-шаблоны, а затем делать их настраиваемыми, добавляя в них слоты. Взглянем на то, как будет выглядеть вышеописанный шаблон с использованием тега
Для того чтобы задать содержимое слота, нужно включить в элемент Как и ранее, тут может быть всё, что угодно. Например:
Обратите внимание на то, что в предыдущем примере мы добавили в слот элемент После обработки вышеописанной разметки браузером будет создано следующее дерево Flattened DOM:
Взглянем на элемент
Кроме того, для включения в
Функциональная форма псевдокласса,
Обычный вариант использования этой возможности заключается в стилизации элементов с помощью тем. Например, часто темы применяют, назначая соответствующий класс тегам
В основе этого подхода лежит механизм, похожий на тот, которым пользуются при работе с тегами Рассмотрим пример:
В роли автора компонента вы ответственны за то, чтобы сообщить его пользователям о том, какие именно пользовательские CSS-свойства они могут использовать. Считайте это частью открытого интерфейса вашего компонента.
Рассмотрим пример: Default content
Взглянем на различные варианты использования этого компонента, и на то, что будет выдано при вызове метода В первом случае мы добавляем в слот собственное содержимое:
Во втором случае мы не заполняем слот собственным содержимым:
Если, однако, передать этому методу параметр
Кроме того, для того, чтобы получить доступ к элементу внутри слота, вы можете вызвать
Вот список событий, которые передаются из теневого дерева DOM (некоторым событиям такое поведение не свойственно):
Практика показывает, что всё больше современных веб-приложений используют Shadow DOM, что позволяет говорить о том, что эту технологию, вероятно, ждёт дальнейшее развитие и распространение. Уважаемые читатели! Пользуетесь ли вы веб-компонентами, построенными на основе технологии Shadow DOM?
Композиция — это одна из важнейших возможностей Shadow DOM, это способ создания веб-приложений, который применяется в процессе написания HTML-кода. В ходе этого процесса программист комбинирует различные строительные блоки (элементы), из которых состоит страница, вкладывая их, при необходимости, друг в друга. Например, это такие элементы, как
, , и другие, используемые для создания интерфейсов веб-приложений, в том числе, выступающие в роли контейнеров для других элементов.
,
,
, по включению в их состав других HTML-элементов в качестве дочерних, и возможности организации особого поведения таких конструкций, состоящих из разных элементов.
имеет средства для рендеринга элементов
в виде выпадающего списка с заранее заданным содержимым элементов такого списка.
Light DOM
Light DOM — это разметка, создаваемая пользователем вашего компонента. Этот DOM находится за пределами теневого DOM компонента и представляет собой дочерний элемент компонента. Представьте себе, что вы создали пользовательский компонент, называемый
, который расширяет возможности стандартного HTML-элемента , и пользователю нужно добавить в этот новый элемент изображение и какой-то текст. Вот как это выглядит:
Элемент
— это пользовательский компонент, описанный программистом самостоятельно, а HTML-код внутри этого компонента — это его Light DOM — то, что добавил в него пользователь этого компонента.
. Это — локальная объектная модель компонента, которая описывает его внутреннюю структуру, изолированный от внешнего мира CSS, и инкапсулирует детали реализации компонента.Flattened DOM
Дерево Flattened DOM представляет собой то, как браузер выводит компонент на экран, объединяя Light DOM и Shadow DOM. Именно такое дерево DOM можно видеть в инструментах разработчика, и именно оно выводится на страницу. Выглядеть это может примерно так:
Шаблоны
Если вам приходится постоянно применять одни и те же структуры в HTML-разметке веб-страниц, полезно будет воспользоваться неким шаблоном вместо того, чтобы снова и снова писать один и тот же код. Подобное было возможно и раньше, но теперь всё значительно упростилось благодаря появлению HTML-тега , который пользуется отличной поддержкой современных браузеров. Этот элемент и его содержимое не выводится в DOM, но с ним можно работать из JavaScript. Рассмотрим простой пример:
Если включить такую конструкцию в состав HTML-разметки страницы, содержимое описываемого ей тега не появится на экране до тех пор, пока не будет явным образом присоединено к DOM документа. Например, это может выглядеть так:
var template = document.getElementById('my-paragraph');
var templateContent = template.content;
document.body.appendChild(templateContent);
Существуют и другие средства, позволяющие достичь того же эффекта, но, как уже было сказано, шаблоны — очень удобный стандартный инструмент, пользующийся хорошей поддержкой браузеров.
Поддержка HTML-шаблонов современными браузерамиcustomElement
позволяет программисту описывать собственные HTML-теги и задавать то, как элементы, создаваемые с помощью этих тегов, будут выглядеть на экране.
:
customElements.define('my-paragraph',
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById('my-paragraph');
let templateContent = template.content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true));
}
});
Самое важное, на что тут надо обратить внимание — это то, что мы присоединили клон содержимого шаблона, сделанный с помощью метода Node.cloneNode (), к теневому корню.
Теперь описанный нами пользовательский элемент можно использовать на обычных веб-страницах следующим образом:
Слоты
У HTML-шаблонов есть несколько недостатков, главный из них заключается в том, что шаблоны содержат статическую разметку, что не позволяет, например, выводить с их помощью содержимое неких переменных для того, чтобы работать с ними так же, как работают со стандартными HTML-шаблонами. Здесь в дело вступает тег
.
:
Если содержимое слота не задано когда элемент включается в разметку, или если браузер не поддерживает работу со слотами, элемент
будет включать в себя лишь стандартное содержимое Default text
.
HTML-код с атрибутом slot
, значение которого эквивалентно имени слота, в который нужно поместить этот код.
Элементы, которые можно помещать в слоты, называются Slotable-элементами., он является так называемым slotted-элементом. У него есть атрибут
slot
, которому присвоено значение my-text
, то есть — то же самое значение, которое использовано в атрибуте name
слота, описанного в шаблоне.
Обратите внимание на элемент #shadow-root
. Это — всего лишь индикатор существования Shadow DOM.Стилизация
Компоненты, которые используют технологию Shadow DOM, можно стилизовать на общих основаниях, они могут определять собственные стили, или предоставлять хуки в форме пользовательских свойств CSS, которые позволяют пользователям компонентов переопределять стили, заданные по умолчанию.▍Стили, описываемые в компонентах
Изоляция CSS — это одно из самых замечательных свойств технологии Shadow DOM. А именно, речь идёт о следующем:
CSS-селекторы, использованные внутри теневого DOM, применяются к содержимому компонента локально. На практике это означает возможность многократного использования одних и тех же идентификаторов и имён классов в разных компонентах и отсутствие необходимости беспокоиться о конфликтах имён. Простые CSS-селекторы означают и более высокую производительность решений, в которых они используются.#shadow-root
, который определяет некоторые стили:
#shadow-root
Все вышеописанные стили являются локальными для #shadow-root
.#shadow-root
внешних таблиц стилей можно использовать тег . Такие стили тоже будут локальными.
▍Псевдокласс : host
Псевдокласс :host
позволяет обращаться к элементу, содержащему теневое дерево DOM и стилизовать этот элемент:
Пользуясь псевдоклассом :host
следует помнить о том, что правила родительской страницы имеют более высокий приоритет, чем те, которые заданы в элементе с использованием этого псевдокласса. Это позволяет пользователям переопределять стили хост-компонента, заданные в нём, извне. Кроме того, псевдокласс :host
работает лишь в контексте теневого корневого элемента, за пределами теневого дерева DOM пользоваться им нельзя.:host(
, позволяет обращаться к хост-элементу, если он соответствует заданному элементу
. Это — отличный способ, позволяющий компонентам инкапсулировать поведение, которое реагирует на действия пользователя или на изменение состояния компонента, и позволяет стилизовать внутренние узлы, основываясь на хост-компоненте:
▍Темы и элементы с псевдоклассом : host-context (
Псевдокласс :host-context(
соответствует хост-элементу, если он или любые его предки соответствуют заданному элементу
. или
:
Псевдокласс :host-context(.lightheme)
будет применяться к
в том случае, если этот элемент является потомком .lightteme
:
:host-context(.lightheme) {
color: black;
background: white;
}
Конструкция :host-context()
может быть полезной для применения тем, но для этой цели лучше использовать хуки с применением пользовательских свойств CSS.▍Стилизация хост-элемента компонента извне
Хост-элемент компонента можно стилизовать извне, используя имя его тега в качестве селектора:
custom-container {
color: red;
}
Внешние стили имеют более высокий приоритет, чем стили, определённые в теневом DOM.
Предположим, пользователь создал следующий селектор:
custom-container {
width: 500px;
}
Он переопределит правило, заданное в самом компоненте:
:host {
width: 300px;
}
Используя этот подход можно стилизовать лишь сам компонент. Как стилизовать внутренние структуры компонента? Для этой цели используются пользовательские свойства CSS.▍Создание хуков стилей с использованием пользовательских свойств CSS
Пользователи могут настраивать стили внутренних структур компонентов если автор компонента предоставляет им хуки стилей, применяя пользовательские свойства CSS.
, но он, в данном случае, применяется к стилям.
Вот что находится внутри теневого дерева DOM:
:host([background]) {
background: var( - custom-container-bg, #CECECE);
border-radius: 10px;
padding: 10px;
}
В данном случае компонент, в качестве цвета фона, использует чёрный, так как именно его задал пользователь. В противном случае цветом фона будет #CECECE
.API JavaScript для работы со слотами
API Shadow DOM предоставляет возможности работы со слотами.▍Событие slotchange
Событие slotchange
вызывается при изменении узлов, помещённых в слот. Например, если пользователь добавляет дочерние узлы в Light DOM или удаляет их из него:
var slot = this.shadowRoot.querySelector('#some_slot');
slot.addEventListener('slotchange', function(e) {
console.log('Light DOM change');
});
Для отслеживания других типов изменений в Light DOM, можно, в конструкторе элемента, использовать MutationObserver
. Подробнее об этом читайте здесь.▍Метод assignedNodes ()
Метод assignedNodes()
может оказаться полезным в том случае, если нужно узнать о том, какие элементы связаны со слотом. Вызов метода slot.assignedNodes()
позволяет узнать о том, какие именно элементы выводятся средствами слота. Использование опции {flatten: true}
позволяет получить стандартное содержимое слота (выводимое в том случае, если к нему не было присоединено никаких узлов).
Представим, что этот слот размещён в компоненте
.assignedNodes()
.
В данном случае вызов assignedNodes()
вернёт [ container text ]
. Обратите внимание на то, что это значение является массивом узлов.
Вызов assignedNodes()
вернёт пустой массив — []
.{flatten: true}
, то его вызов для того же самого элемента выдаст его содержимое, выводимое по умолчанию: [
Default content
]
.assignedNodes()
, что позволить узнать о том, какому из слотов компонента назначен ваш элемент.Модель событий
Поговорим о том, что происходит при всплытии события, возникшего в теневом дереве DOM. Цель события задаётся с учётом инкапсуляции, поддерживаемой технологией Shadow DOM. Когда событие перенаправляется, это выглядит так, как будто оно исходит от самого компонента, а не от его внутреннего элемента, который находится в теневом дереве DOM и является частью этого компонента.blur
, focus
, focusin
, focusout
.click
, dblclick
, mousedown
, mouseenter
, mousemove
и другие.wheel
.beforeinput
, input
.keydown
, keyup
.compositionstart
, compositionupdate
, compositionend
.dragstart
, drag
, dragend
, drop
, и так далее.Пользовательские события
Пользовательские события по умолчанию не покидают пределов теневого дерева DOM. Если вы хотите вызвать событие, и требуется, чтобы оно покинуло пределы Shadow DOM, нужно снабдить его параметрами bubbles: true
и composed: true
. Вот как выглядит вызов подобного события:
var container = this.shadowRoot.querySelector('#container');
container.dispatchEvent(new Event('containerchanged', {bubbles: true, composed: true}));
Поддержка Shadow DOM браузерами
Для того чтобы узнать, поддерживает ли браузер технологию Shadow DOM, можно проверить наличие attachShadow
:
const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;
Вот сведения о поддержке этой технологии различными браузерами.
Поддержка технологии Shadow DOM в браузерахИтоги
Теневое дерево DOM ведёт себя не так, как обычное дерево DOM. В частности, по словам автора данного материала, в библиотеке SessionStack это выражается в усложнении процедуры отслеживания изменений DOM, сведения о которых нужны для воспроизведения того, что происходило со страницей. А именно, для отслеживания изменений используется MutationObserver
. При этом теневое дерево DOM не вызывает события MutationObserver
в глобальной области видимости, что приводит к необходимости использования особых подходов для работы с компонентами, использующими Shadow DOM.