что такое shadow dom
Shadow DOM
Теневой DOM («Shadow DOM») используется для инкапсуляции. Благодаря ему в компоненте есть собственное «теневое» DOM-дерево, к которому нельзя просто так обратиться из главного документа, у него могут быть изолированные CSS-правила и т.д.
Встроенный теневой DOM
Задумывались ли вы о том, как устроены и стилизованы сложные браузерные элементы управления?
Браузер рисует их своими силами и по своему усмотрению. Их DOM-структура обычно нам не видна, но в инструментах разработчика можно её посмотреть. К примеру, в Chrome для этого нужно активировать пункт «Show user agent shadow DOM».
После этого выглядит так:
То, что находится под #shadow-root – и называется «shadow DOM» (теневой DOM).
Мы не можем получить доступ к теневому DOM встроенных элементов с помощью обычных JavaScript-вызовов или с помощью селекторов. Это не просто обычные потомки, это мощное средство инкапсуляции.
Ещё раз заметим, что pseudo – нестандартный атрибут. Если говорить хронологически, то сначала браузеры начали экспериментировать с инкапсуляцией внутренних DOM-структур для элементов, а уже потом, через некоторое время, появился стандарт Shadow DOM, который позволяет делать то же самое нам, разработчикам.
Далее мы воспользуемся современным стандартом Shadow DOM, описанным в спецификации DOM spec и других спецификациях.
Теневое дерево
Каждый DOM-элемент может иметь 2 типа поддеревьев DOM:
Если у элемента имеются оба поддерева, браузер отрисовывает только теневое дерево. Также мы всё же можем задать «композицию» теневого и обычного деревьев. Позже в главе Слоты теневого DOM, композиция мы рассмотрим детали.
Теневое дерево можно использовать в пользовательских элементах (Custom Elements), чтобы спрятать внутренности компонента и применить к ним локальные стили.
Например, этот элемент прячет свой внутренний DOM в теневом дереве:
А вот как получившийся DOM выглядит в инструментах разработчика в Chrome, весь контент внутри «#shadow-root»:
Итак, вызов elem.attachShadow(
Есть два ограничения:
Свойство mode задаёт уровень инкапсуляции. У него может быть только два значения:
Элемент с корнем теневого дерева называется – «хозяин» (host) теневого дерева, и он доступен в качестве свойства host у shadow root:
Инкапсуляция
Теневой DOM отделён от главного документа:
Ссылки
Итого
Теневой DOM – это способ создать свой, изолированный, DOM для компонента.
Элементы теневого DOM:
Теневой DOM, если имеется, отрисовывается браузером вместо обычных потомков (light DOM). В главе Слоты теневого DOM, композиция мы разберём, как делать их композицию.
Shadow DOM
Итак, что же такое shadow DOM:
Shadow DOM (или теневая модель документа) — часть документа, реализующая инкапсуляцию в DOM дереве. Она (теневая модель) является частью документа и встраивается непосредственно внутрь страницы.
Для упрощения отладки shadow DOM, в хроме можно включить отображение в веб-инспекторе (Settings — General — Show shadow DOM).
Надо заметить, что в стандарте реализуемая инкапсуляция называется функциональной, поскольку shadow DOM встраивается в документ и является одной из многих его частей, работающих «независимо» (более-менее независимо) друг от друга. Соответственно, при проектировании реализации, нужно было установить функциональные границы в дереве документа, чтобы как-то оперировать с множеством таких «независимых» фрагментов. Для решения проблемы инкапсуляции, и была введена новая абстракция — shadow DOM, позволяющая создавать несколько DOM деревьев в пределах одного родительского дерева и был разработан документ, описывающий ее.
Дочернее дерево размещается внутри некоторого элемента на странице. Функциональные границы между главным деревом документа и теневым называются shadow boundaries (теневые границы). Элемент, который размещает в себе теневое дерево, называется shadow host, а корень теневого дерево, соответственно, называется shadow root.
Во время рендеринга shadow tree занимает место содержимого shadow host (элемента).
Пример реализации в chromium:
Insertion points
Для композиции потомков shadow host и shadow tree используются insertion points. Insertion points определяют местонахождение потомков shadow host в shadow tree. При рендеринге shadow tree потомки проецируются в это место. Механизм, определяющий какие потомки shadow host будут спроецированы в insertion point называется distribution.
Псевдо-элемент ::distributed()
::distributed(selector) — функциональный псевдо-элемент принимающий относительный селектор в качестве аргумента. Он представляет отношение между insertion point в shadow tree и элементом, перенесенным в insertion point.
Реализация (chrome canary only):
Один shadow host может вмещать в себя несколько shadow tree — они будут отображены в порядке их добавления. Такой набор деревьев называется shadow stack. Более «старый» shadow tree так же можно переносить в другой shadow tree посредством shadow insertion point.
Reprojection (перепроецирование)
Перепроецирование это ситуация, при которой первое shadow tree уже имеет insertion point, а второй shadow tree имеет shadow insetion point, при этом контент, взятый из shadow host сначала проецируется в первом shadow tree, а затем во втором.
Псевдо-элементы (в контексте shadow DOM)
In certain situations, the author of a shadow tree may wish to designate one or more elements from that tree as a structural abstraction that provides additional information about the contents of the shadow tree.
В определенных ситуациях, автору shadow tree захочется назначить один или несколько элементов из shadow tree как стукртурную абстракцию, дающую дополнительную информацию о контенте shadow tree.
Что я понимаю как возможность использовать css селекторы вне shadow tree для доступа к элементам внутри него:
События
Некоторые события пропускаются через shadow boundary, некоторые нет. Исключение составляют mutation events — они вообще не должны возникать в shadow tree и, соответственно, переходить через shadow boundary. При прохождении события через shadow boundary у него меняется event.target для поддержания инкапсуляции.
Вот интересный пример:
События спроецированного элемента всплывают в shadow host, как-будто он все еще находится непосредственно внутри shadow host. События first-inner-element не всплывают в shadow host, в отличие от second-inner-element, который абсолютно спозиционирован и вынесен за пределы shadow host (при этом event.target сменился).
Стили
Есть два метода, позволяющие манипулировать стилями shadow tree:
shadowRoot.resetStyleInheritance (false by default)
Сбрасывает наследование стилей для shadow tree (стили снаружи не применяются на shadow tree).
shadowRoot.applyAuthorStyles (false by default)
Применяет стили авторского (главного) документа.
Можно сказать, что некоторой «инкапсуляции» для html не хватало. Это открывает большие возможности по созданию и шаблонизации различных, заранее подготовленных, виджетов на странице. Удивляет только отсутствие инкапсуляции JavaScript кода внутри виджетов, хотя мне казалось бы это довольно логичным.
Shadow DOM
Теневой DOM («Shadow DOM») используется для инкапсуляции. Благодаря ему в компоненте есть собственное «теневое» DOM-дерево, к которому нельзя просто так обратиться из главного документа, у него могут быть изолированные CSS-правила и т.д.
Встроенный теневой DOM
Задумывались ли вы о том, как устроены и стилизованы сложные браузерные элементы управления?
Браузер рисует их своими силами и по своему усмотрению. Их DOM-структура обычно нам не видна, но в инструментах разработчика можно её посмотреть. К примеру, в Chrome для этого нужно активировать пункт «Show user agent shadow DOM».
После этого выглядит так:
То, что находится под #shadow-root – и называется «shadow DOM» (теневой DOM).
Мы не можем получить доступ к теневому DOM встроенных элементов с помощью обычных JavaScript-вызовов или с помощью селекторов. Это не просто обычные потомки, это мощное средство инкапсуляции.
Ещё раз заметим, что pseudo – нестандартный атрибут. Если говорить хронологически, то сначала браузеры начали экспериментировать с инкапсуляцией внутренних DOM-структур для элементов, а уже потом, через некоторое время, появился стандарт Shadow DOM, который позволяет делать то же самое нам, разработчикам.
Далее мы воспользуемся современным стандартом Shadow DOM, описанным в спецификации DOM spec и других спецификациях.
Теневое дерево
Каждый DOM-элемент может иметь 2 типа поддеревьев DOM:
Если у элемента имеются оба поддерева, браузер отрисовывает только теневое дерево. Также мы всё же можем задать «композицию» теневого и обычного деревьев. Позже в главе Слоты теневого DOM, композиция мы рассмотрим детали.
Теневое дерево можно использовать в пользовательских элементах (Custom Elements), чтобы спрятать внутренности компонента и применить к ним локальные стили.
Например, этот элемент прячет свой внутренний DOM в теневом дереве:
А вот как получившийся DOM выглядит в инструментах разработчика в Chrome, весь контент внутри «#shadow-root»:
Итак, вызов elem.attachShadow(
Есть два ограничения:
Свойство mode задаёт уровень инкапсуляции. У него может быть только два значения:
Элемент с корнем теневого дерева называется – «хозяин» (host) теневого дерева, и он доступен в качестве свойства host у shadow root:
Инкапсуляция
Теневой DOM отделён от главного документа:
Ссылки
Итого
Теневой DOM – это способ создать свой, изолированный, DOM для компонента.
Элементы теневого DOM:
Теневой DOM, если имеется, отрисовывается браузером вместо обычных потомков (light DOM). В главе Слоты теневого DOM, композиция мы разберём, как делать их композицию.
Что такое Shadow DOM?
Ранее я написал статью «Что такое DOM?» Напомним, что объектная модель документа является представление HTML документа в виде дерева объектов. Оно используется браузерами для определения того, что отображать на странице, и кодом Javascript для изменения содержимого, структуры или стиля страницы.
Для примера взглянем на следующий HTML документ:
Этот документ может быть представлен в виде DOM дерева.
Вы, возможно, слышали о таких терминах, как «Shadow DOM» и «Virtual DOM». Хотя они, конечно, связаны с оригинальным DOM, но по сути своей это совершенно разные концепции. В этой статье я расскажу, что конкретно представляет собой теневой DOM и чем он отличается от исходного DOM. В следующей статье я расскажу что такое виртуальный DOM.
Все вокруг глобально 👍🏾! Стоп, все глобально 👎🏾
Все элементы и стили в HTML-документе и, следовательно, в DOM, находятся в одной большой глобальной области. Любой элемент на странице может быть доступен с помощью метода document.querySelector(), независимо от того, насколько глубоко он вложен в документ или где он находится. Точно так же CSS, примененный к документу, может выбрать любой элемент, независимо от того, где он находится.
Такое поведение может быть очень удобным, если мы хотим применить стили ко всему документу. Невероятно полезно иметь возможность выбирать каждый отдельный элемент на странице и устанавливать, например, их размеры одной строкой.
С другой стороны, бывают случаи, когда элемент требует полной изоляции, и мы не хотим, чтобы на него влияли даже глобальные стили. Хорошим примером этого являются сторонние виджеты, такие как кнопка «Follow» для Twitter на некоторых страницах.
Такая кнопка может отображаться как обычная ссылка
DOM внутри DOM
Вы можете думать о теневом DOM как о «DOM внутри DOM». Это собственное изолированное дерево DOM со своими элементами и стилями, полностью изолированное от исходного DOM.
Хотя только недавно его стали использовать программисты, теневой DOM годами использовался пользовательскими агентами для создания и оформления сложных компонентов, таких как элементы формы. Для примера возьмем элемент ввода диапазона. Чтобы создать его на странице, все, что нам нужно сделать, это добавить следующий элемент:
Если мы посмотрим глубже, мы увидим, что этот один элемент фактически состоит из нескольких меньших элементов
Как работает shadow DOM
Сначала мы начнем с shadow host (теневого хоста). Это обычный элемент HTML в исходном DOM, к которому мы хотим присоединить новый теневой DOM. Для такого компонента, как кнопка «Follow», он также может содержать запасной элемент, который мы хотели бы отобразить, если Javascript не был включен на странице или теневой DOM не поддерживается.
Это создаст пустой shadow root (теневой корень) как дочерний элемент нашего теневого хоста. Теневой корень — это начало нового теневого DOM, так же, как элемент является началом исходного DOM. Мы можем увидеть наш теневой корень в инспекторе devtools с помощью #shadow-root.
Хотя теперь дочерние элементы HTML видны в инспекторе, они больше не видны на странице, так как заработал теневой корень.
Далее мы добавим этот новый элемент в нашу теневую DOM так же, как добавляем любой элемент в качестве дочернего элемента к другому с помощью метода appendChild().
На данный момент, наш элемент должен выглядет как то так:
Наконец, мы можем добавить несколько стилей, создав элемент и добавив его к теневому корню.
В итоге наш элемент должен выглядеть как то так:
DOM против shadow DOM
В некотором смысле, теневой DOM является «облегченной» версией DOM. Как и DOM, он является представлением элементов HTML, которое используется для определения того, что следует отображать на странице, и позволяет изменять элементы. Но в отличие от DOM, теневой DOM не использует глобальные стили документа. Теневой DOM, как следует из названия, всегда присоединен к элементу в обычном DOM. Без базовых элементов DOM, теневой DOM не существует.
Как работает JS: технология Shadow DOM и веб-компоненты
Сегодня, в переводе 17 части материалов, посвящённых особенностям всего, что так или иначе связано с JavaScript, речь пойдёт о веб-компонентах и о различных стандартах, которые направлены на работу с ними. Особое внимание здесь будет уделено технологии Shadow DOM.
Обзор
Веб-компоненты — это семейство API, предназначенных для описания новых элементов DOM, подходящих для повторного использования. Функционал таких элементов отделён от остального кода, их можно применять в веб-приложениях собственной разработки.
Существует четыре технологии, относящиеся к веб-компонентам:
Технология Shadow DOM
Тут предполагается, что вы уже знакомы с концепцией DOM и с соответствующими API. Если это не так — можете почитать этот материал.
Shadow DOM — это, в целом, то же самое, что и обычный DOM, но с двумя отличиями:
Это изолированное поддерево называют shadow tree (теневое дерево). Элемент, к которому присоединено такое дерево, называется shadow host (теневой хост-элемент). Всё, что добавляется в теневое поддерево DOM, оказывается локальным для элемента, к которому оно присоединено, в том числе — стили, описываемые с помощью тегов Launch
Шаблоны
Если включить такую конструкцию в состав HTML-разметки страницы, содержимое описываемого ей тега
не появится на экране до тех пор, пока не будет явным образом присоединено к DOM документа. Например, это может выглядеть так:
Существуют и другие средства, позволяющие достичь того же эффекта, но, как уже было сказано, шаблоны — очень удобный стандартный инструмент, пользующийся хорошей поддержкой браузеров.
Поддержка HTML-шаблонов современными браузерами
Шаблоны полезны и сами по себе, но в полной мере их возможности раскрываются при использовании с пользовательскими элементами. Пользовательские элементы — это тема для отдельного материала, а сейчас, для понимания происходящего, достаточно учитывать то, что API браузеров customElement позволяет программисту описывать собственные HTML-теги и задавать то, как элементы, создаваемые с помощью этих тегов, будут выглядеть на экране.
Определим веб-компонент, который использует наш шаблон в качестве содержимого для своего теневого DOM. Назовём этот новый элемент :
Самое важное, на что тут надо обратить внимание — это то, что мы присоединили клон содержимого шаблона, сделанный с помощью метода Node.cloneNode(), к теневому корню.
Так как мы присоединяем содержимое шаблона к теневому DOM, мы можем включить в шаблон некую информацию о стилизации, в элементе
Теперь описанный нами пользовательский элемент можно использовать на обычных веб-страницах следующим образом:
Слоты
Слоты можно воспринимать как местозаполнители, которые позволяют включать в шаблон собственный HTML-код. Это позволяет создавать универсальные HTML-шаблоны, а затем делать их настраиваемыми, добавляя в них слоты.
Взглянем на то, как будет выглядеть вышеописанный шаблон с использованием тега :
Как и ранее, тут может быть всё, что угодно. Например:
Элементы, которые можно помещать в слоты, называются Slotable-элементами.
После обработки вышеописанной разметки браузером будет создано следующее дерево Flattened DOM:
Стилизация
Компоненты, которые используют технологию Shadow DOM, можно стилизовать на общих основаниях, они могут определять собственные стили, или предоставлять хуки в форме пользовательских свойств CSS, которые позволяют пользователям компонентов переопределять стили, заданные по умолчанию.
▍Стили, описываемые в компонентах
Изоляция CSS — это одно из самых замечательных свойств технологии Shadow DOM. А именно, речь идёт о следующем:
▍Псевдокласс :host
Псевдокласс :host позволяет обращаться к элементу, содержащему теневое дерево DOM и стилизовать этот элемент:
Пользуясь псевдоклассом :host следует помнить о том, что правила родительской страницы имеют более высокий приоритет, чем те, которые заданы в элементе с использованием этого псевдокласса. Это позволяет пользователям переопределять стили хост-компонента, заданные в нём, извне. Кроме того, псевдокласс :host работает лишь в контексте теневого корневого элемента, за пределами теневого дерева DOM пользоваться им нельзя.
▍Темы и элементы с псевдоклассом :host-context( )
Обычный вариант использования этой возможности заключается в стилизации элементов с помощью тем. Например, часто темы применяют, назначая соответствующий класс тегам или :
Конструкция :host-context() может быть полезной для применения тем, но для этой цели лучше использовать хуки с применением пользовательских свойств CSS.
▍Стилизация хост-элемента компонента извне
Хост-элемент компонента можно стилизовать извне, используя имя его тега в качестве селектора:
Внешние стили имеют более высокий приоритет, чем стили, определённые в теневом DOM.
Предположим, пользователь создал следующий селектор:
Он переопределит правило, заданное в самом компоненте:
Используя этот подход можно стилизовать лишь сам компонент. Как стилизовать внутренние структуры компонента? Для этой цели используются пользовательские свойства CSS.
▍Создание хуков стилей с использованием пользовательских свойств CSS
Пользователи могут настраивать стили внутренних структур компонентов если автор компонента предоставляет им хуки стилей, применяя пользовательские свойства CSS.
Вот что находится внутри теневого дерева DOM:
В роли автора компонента вы ответственны за то, чтобы сообщить его пользователям о том, какие именно пользовательские CSS-свойства они могут использовать. Считайте это частью открытого интерфейса вашего компонента.
API JavaScript для работы со слотами
API Shadow DOM предоставляет возможности работы со слотами.
▍Событие slotchange
Событие slotchange вызывается при изменении узлов, помещённых в слот. Например, если пользователь добавляет дочерние узлы в Light DOM или удаляет их из него:
▍Метод assignedNodes()
Метод assignedNodes() может оказаться полезным в том случае, если нужно узнать о том, какие элементы связаны со слотом. Вызов метода slot.assignedNodes() позволяет узнать о том, какие именно элементы выводятся средствами слота. Использование опции
В первом случае мы добавляем в слот собственное содержимое:
Во втором случае мы не заполняем слот собственным содержимым:
Если, однако, передать этому методу параметр
Модель событий
Поговорим о том, что происходит при всплытии события, возникшего в теневом дереве DOM. Цель события задаётся с учётом инкапсуляции, поддерживаемой технологией Shadow DOM. Когда событие перенаправляется, это выглядит так, как будто оно исходит от самого компонента, а не от его внутреннего элемента, который находится в теневом дереве DOM и является частью этого компонента.
Вот список событий, которые передаются из теневого дерева DOM (некоторым событиям такое поведение не свойственно):
Пользовательские события
Поддержка Shadow DOM браузерами
Для того чтобы узнать, поддерживает ли браузер технологию Shadow DOM, можно проверить наличие attachShadow :
Вот сведения о поддержке этой технологии различными браузерами.
Поддержка технологии Shadow DOM в браузерах
Итоги
Практика показывает, что всё больше современных веб-приложений используют Shadow DOM, что позволяет говорить о том, что эту технологию, вероятно, ждёт дальнейшее развитие и распространение.
Уважаемые читатели! Пользуетесь ли вы веб-компонентами, построенными на основе технологии Shadow DOM?