Shadow DOM
В современных сайтах часто используется много разных элементов, стилей и компонентов, и иногда бывает так, что стили одного элемента начинают влиять на другие. В итоге всё это накладывается друг на друга и страница начинает выглядеть странно: вёрстка плывёт, контент не на своём месте и всё такое.
Чтобы такого не случалось, разработчики изолируют стили элементов, используя теневой DOM Shadow DOM
. Это то же самое, что и обычный DOM, но защищённый от внешнего воздействия. Стили и скрипты внутри теневого DOM не влияют на остальные элементы на странице и не зависят от них. Такие элементы не сломают стили страницы, если их использовать в другом проекте. Рассказываем, что такое теневой DOM и как с ним работать на практике.
Что такое Document Object Model (DOM)
DOM (Document Object Model) — это представление HTML-документа в виде иерархического дерева узлов, где каждый элемент на странице — это отдельный узел, или DOM-элемент.
Проще говоря, страница — это дерево, а каждый элемент на ней — это ветки и листья. Используя DOM, мы можем с помощью JS-скриптов дотянуться до любого элемента на странице и что-то с ним сделать: поменять текст, скрыть что-то или добавить новые части. Если нужно поменять заголовок на странице, это можно сделать через DOM, не перезагружая страницу. То есть DOM — это то, с помощью чего JavaScript может «общаться» с HTML-страницей и делать её интерактивной.
Схематично это можно нарисовать как-то так:
DOM появляется, когда браузер загружает HTML-документ. Браузер читает HTML-код и на его основе собирает для себя структуру в виде дерева. Затем всё это браузер помещает в объект document
, к которому мы можем обращаться, в котором можем искать нужные элементы и что-то с ними делать.
Что такое Shadow DOM
DOM-элемент на странице может иметь не только свой видимый контент, но и скрытое, изолированное поддерево внутри. Это и есть теневой DOM, который в отличие от обычного, светлого, всегда скрыт.
Получается, что DOM-элемент на странице может иметь два дерева:
Light DOM
(светлый DOM) — это обычное дерево элементов, которые мы видим в HTML-разметке и с которыми работаем черезdocument.querySelector
илиgetElementsBy*
. Это стандартная часть DOM, которую можно посмотреть в инструментах разработчикаShadow DOM
(теневой DOM) — это скрытое дерево элементов, которое создаётся разработчиком с помощью JavaScript. Оно изолировано от остального документа, поэтому его стили и скрипты не пересекаются с внешним кодом. Элементы в теневом DOM нельзя найти через обычные методы, поскольку они спрятаны внутри элемента, к которому прикреплены
Как устроен Shadow DOM
Допустим, есть элемент div
. Этот div
— часть обычного DOM (светлого дерева). Если разработчик создаёт для него теневой DOM, то у этого div
появляется теневой корень, под которым строится теневое дерево. Это теневое дерево полностью скрыто внутри этого div
, хотя вся эта конструкция и остаётся в обычном DOM и не видна пользователю.
Элемент, к которому мы прикрепляем теневое дерево, называется shadow host
. Внутри него находится shadow root
теневой корень, который прячет всю разметку и стили. Именно через этот узел происходит доступ к стилям, которые находятся внутри теневого DOM.
Разметка, стили и логика теневых элементов не влияют на остальную часть документа и сами не подвержены стороннему влиянию. Получается, что теневой DOM позволяет создавать отдельные мини-страницы внутри основной страницы, где элементы живут по своим правилам.
Shadow root
выполняет роль контейнера для всех элементов и стилей, которые спрятаны в теневом дереве. Он целиком отделён от основного DOM, поэтому стили и скрипты внутри него не могут даже случайно взаимодействовать с внешней частью страницы. Такой элемент компонент всегда будет выглядеть и работать одинаково, независимо от остальной части сайта. Теневой DOM можно добавлять к разным элементам, поэтому на странице может быть сразу несколько теневых DOM.
Ключевые типы в мире Shadow DOM:
Host
элемент, к которому присоединёнshadow root
Shadow root
входная точка теневого DOM, место где начинается ваша инкапсулированная структураShadow tree
узлы и элементы, скрытый внутриshadow root
Light DOM
обычный DOM, который содержит элементы для проецирования вshadow DOM
через слоты
Как используется Shadow DOM
Механизм теневого DOM используют при создании пользовательских компонентов — виджетов или элементов интерфейса, логика и стили которых должны быть изолированы от остального контента страницы.
Допустим, мы разрабатываем виджет поддержки клиентов, который будет размещаться на разных сайтах. Чтобы он везде работал корректно, мы изолируем все его внутренние элементы от остальной страницы. В теневой DOM положим разметку виджета (контейнер чата, поле ввода и кнопку отправки), стили этих элементов, и скрипты, управляющие поведением. Такая изоляция будет гарантировать, например, что кнопка отправки всегда будет выглядеть так, как задумано, независимо от того, какие правила для кнопок заданы на сайте. Логика отправки сообщений тоже защищена — внешний код не может вмешаться в работу виджета.
В большинстве случаев стили компонентов с теневым DOM можно посмотреть через инструменты разработчика. В Chrome в настройках нужно поставить галочку «Показывать теневой DOM», и если на странице будут элементы теневого DOM, то их можно будет посмотреть.
Теперь мы увидим всё, что находится на странице на самом деле:
Теневой DOM можно создавать в двух режимах:
open
открытый режим, можно взаимодействовать, например программно получить к нему доступ через JavaScriptclosed
закрытый режим, нельзя взаимодействовать, можно только посмотреть содержимое теневого DOM, но доступ к нему через код невозможен. Это значит, что никакие внешние скрипты, включая браузерные расширения, не смогут взаимодействовать с закрытым теневым DOM через код
Как создать Shadow DOM
Теневой DOM можно прикрепить только к тем элементам, которые могут быть контейнерами для этой изолированной области:
div
span
section
article
header
footer
aside
nav
main
Представьте, что вы создаёте веб-сайт и хотите добавить на него кнопку с красивым дизайном, которая будет использоваться в нескольких местах сайта. Вы хотите, чтобы стиль этой кнопки был уникальным и не конфликтовал с другими стилями на странице. Использование Shadow DOM позволяет решить эту задачу:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Теневой DOM</title>
</head>
<body>
<!-- контейнер для теневого DOM -->
<div id="shadow-element"></div>
<script>
// получаем элемент с id "shadow-element"
const shadowElement = document.getElementById('shadow-element');
// создаём теневой корень внутри элемента и задаём режим 'open' (открытый доступ к теневому DOM)
const shadowRoot = shadowElement.attachShadow({mode: 'open'});
// вставляем стили и элемент
shadowRoot.innerHTML = `
<style>
button {
background-color: #4CAF50; /* Зелёный */
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border: none;
border-radius: 8px;
}
</style>
<button>Нажми</button>`;
// получаем элемент button из теневого DOM
const paragraphAct = shadowRoot.querySelector('button');
// добавляем обработчик события для элемента button
paragraphAct.addEventListener('click', () => {
alert('Текст внутри теневого DOM');
});
// меняем програмно текст у кнопки
shadowElement.shadowRoot.querySelector('button').innerHTML = `Нажми на меня`;
</script>
</body>
</html>
Как создать Shadow DOM со slot
App.vue<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Теневой DOM</title>
</head>
<body>
<!-- контейнер для теневого DOM -->
<div id="shadow-element">
<button slot="button1">Кнопка1</button>
<button slot="button2">Кнопка1</button>
</div>
<script>
// получаем элемент с id "shadow-element"
const shadowElement = document.getElementById('shadow-element');
// создаём теневой корень внутри элемента и задаём режим 'open' (открытый доступ к теневому DOM)
const shadowRoot = shadowElement.attachShadow({mode: 'open'});
// вставляем стили и элемент
shadowRoot.innerHTML = `
<style>
button {
background-color: #4CAF50; /* Зелёный */
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border: none;
border-radius: 8px;
}
</style>
<div>
Кнопка1:
<slot name="button1"></slot>
</div>
<div>
Кнопка2:
<slot name="button2"></slot>
</div>
`;
// получаем элемент slot из теневого DOM
const paragraphAct = shadowRoot.querySelectorAll('slot');
// добавляем обработчик события для элемента slot
paragraphAct.forEach(function($element) {
$element.addEventListener('click', () => {
alert('Текст внутри теневого DOM');
});
});
</script>
</body>
</html>