Что такое Shadow DOM и для чего он нужен?
Последние годы, вы могли слышать такой термин - “Shadow DOM” и “Virtual DOM”, они конечно связаны с оригинальным DOM (а именно Document Object Model - объектная модель документа, например простейшей html страницы.), но строятся на иных концептах.
Допустим у нас имеется пустая HTML страница с кодом:
<!doctype html> <html lang='ru'> <head> <title>Просто HTML документ</title> </head> <body> <h1>HTML документ</h1> <p>почему бы и нет?</p> <p>Действительно.</p> </body> </html>
Этот код имеет такую иерархию.
html -head --title --- Просто HTML документ -body --h1 ---HTML документ --p ---почему бы и нет?
Все элементы в таком документе HTML (и следовательно соответствующие им DOM объекты), находятся в глобальной области видимости. и доступны с помощью метода document.querySelector(), который вне зависимости от того на какой глубине этот элемент находится, найдет его (а если не один, то найдёт все). Таким образом на чистом Javascript можно добавлять любому элементу стиль, или подписаться на события исходящие от него, например ‘click’.
Но в некоторых ситуациях неплохо бы изолировать такой объект от какого нибудь глобального стиля, чтобы он был независим, например какой то виджет, или блок рекламы, или другая причина, например безопасность.
Shadow DOM был как раз создан, для того чтобы нативно изолировать (инкапсулировать) элемент на странице.
DOM в DOM
Вы можете рассматривать Shadow DOM как ДОМ В ДОМе, который изолирован , не наследует от внешнего и использует только свои стили.
Это не новый концепт, и используется издавна браузерами и другими (user agent) чтобы создавать более сложные нативные элементы
<input type='range'>
Например такой тег ( если включить отображение Shadow DOM в браузере ), даст такое дерево элементов в внутри:
Тут мы видим что тег <input></input> скрывает в себе более мелкие элементы, которые изолированы от внешнего кода, контролируют свое состояние, например размеры, цвет и положение указателя. Это и реализовано с помощью Shadow DOM, снаружи виден только элемент input, а всё что находится внутри него нам не подчиняется общим стилям.
Как работает Shadow DOM ?
Чтобы понять лучше как он работает, предлагаю создать кнопку Твиттера.
Для начала, начнём с родительского элемента. Это обычный элемент HTML в который мы хотим положить shadow содержимое. В случае создания компонента простой кнопки Follow, он так же будет содержать элемент который придётся отображать в случае если Javascript или Shadow DOM отключены в браузере.
<span class='shadow-host'> <a href='https://twitter.com/twitter'> Follow @twitter </a> </span>
Для вставки shadow DOM в родительский элемент используется метод attachShadow()
Выполните эти строки в консоли браузера, и увидите результат.
const shadowEl = document.querySelector('.shadow-host'); const shadow = shadowEl.attachShadow({mode: 'open'});
Это создаст корневой shadow элемент как дочерний выбранному элементу. Через панель разработчика в браузере мы можем лицезреть что наш span элемент присутствует на странице, однако его содержимое видимое в консоле и находящееся в #shadow-root, перестало быть видимым на странице.
Далее, неплохо бы создать shadow содержимое, - новую структуру DOM. Для создания кнопки, непобходимо лишь создать новый элемент <a> который будет примерно таким как и существующий, но с иконкой.
const link = document.createElement('a'); link.href = shadowEl.querySelector('a').href; link.innerHTML = ` <span aria-label='Twitter icon'></span> ${shadowEl.querySelector('a').textContent}`;
Мы добавим этот новый элемент в наш shadow DOM с помощью обычного метода добавления child элемента в родительский.
И наконец, можно добавить стили:
const styles = document.createElement('style'); styles.textContent = ` a, span { vertical-align: top; display: inline-block; box-sizing: border-box; } a { height: 20px; padding: 1px 8px 1px 6px; background-color: #1b95e0; color: #fff; border-radius: 3px; font-weight: 500; font-size: 11px; font-family:'Helvetica Neue', Arial, sans-serif; line-height: 18px; text-decoration: none; } a:hover { background-color: #0c7abf; } span { position: relative; top: 2px; width: 14px; height: 14px; margin-right: 3px; background: transparent 0 0 no-repeat; background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2072%2072%22%3E%3Cpath%20fill%3D%22none%22%20d%3D%22M0%200h72v72H0z%22%2F%3E%3Cpath%20class%3D%22icon%22%20fill%3D%22%23fff%22%20d%3D%22M68.812%2015.14c-2.348%201.04-4.87%201.744-7.52%202.06%202.704-1.62%204.78-4.186%205.757-7.243-2.53%201.5-5.33%202.592-8.314%203.176C56.35%2010.59%2052.948%209%2049.182%209c-7.23%200-13.092%205.86-13.092%2013.093%200%201.026.118%202.02.338%202.98C25.543%2024.527%2015.9%2019.318%209.44%2011.396c-1.125%201.936-1.77%204.184-1.77%206.58%200%204.543%202.312%208.552%205.824%2010.9-2.146-.07-4.165-.658-5.93-1.64-.002.056-.002.11-.002.163%200%206.345%204.513%2011.638%2010.504%2012.84-1.1.298-2.256.457-3.45.457-.845%200-1.666-.078-2.464-.23%201.667%205.2%206.5%208.985%2012.23%209.09-4.482%203.51-10.13%205.605-16.26%205.605-1.055%200-2.096-.06-3.122-.184%205.794%203.717%2012.676%205.882%2020.067%205.882%2024.083%200%2037.25-19.95%2037.25-37.25%200-.565-.013-1.133-.038-1.693%202.558-1.847%204.778-4.15%206.532-6.774z%22%2F%3E%3C%2Fsvg%3E); } `;
shadow.appendChild(styles);
Вот и результат.
В определённой степени, Shadow DOM это облегчённая версия DOM. Как и DOM это визуальное отображение элементов HTML используемое для определения того что нужно отображать на странице а что нет, а так же для изменения отображения. Но в отличии от DOM, Shadow DOM не может быть автономным, он должен быть всегда вставлен (прикреплён) в уже существющий элемент обычного DOM, без последнего, первый не может существовать.
В настоящее время, Shadow DOM широко используется в современных веб фреймворках, таких как Angular, Vue.js и React.js. Именно благодаря этому мы можем создавать динамичные страницы и приложения, буквально вся страница, содержащая тысячи элементов может быть моментально перерисована в зависимости от определённых условий.
Комментариев пока что ниту, может ты чирканёшь первый, мне будет очень приятно!=)