Обеспечение единообразия стилей и поведения веб-приложений в разных браузерах и на разных платформах является одной из первостепенных задач фронтенд-разработчика. Среди множества инструментов и способов их реализации является использование теневого DOM-дерева - Shadow DOM.

Однако прежде чем погрузиться в особенности этой технологии, вспомним, что вцелом из себя представляет DOM.

Что такое DOM

Согласно определению, DOM (Document Object Model - объектная модель документа) - это программный интерфейс, который позволяет взаимодействовать со структурой, стилем и содержимым веб-страницы. Специальный парсер анализирует HTML-документ и создает древовидную структуру, каждый элемент которой называется узлом (node). По похожему принципу работает CSS парсер, который создает древовидную структуру из стилей. Созданные структуры объединяются и происходит рендеринг страницы, т.е. ее отрисовка. Новички часто заблуждаются, когда думают, что код HTML - это и есть DOM. На деле, DOM - это результат обработки HTML, это иной его вид. И когда мы хотим повлиять на HTML страницы, мы обращаемся именно к DOM.

Document Object Model

DOM в инспекторе

Манипулировать DOM-деревом не так-то просто и удобно, и, что самое главное, это приносит много проблем, связанных с производительностью. С помощью JavaScript мы можем получить доступ и внести изменения в DOM, обратившись к объекту браузера document и используя глобальные свойства или методы. В настоящее время существуют две основные концепции DOM, появившиеся вместе с прогрессивными веб-фреймворками, такими как Angular, React или Vue: Shadow DOM и Virtual DOM.

Что такое Shadow DOM

Теневой DOM - это технология, которая позволяет разработчикам создавать встроенные и независимые от основного DOM узлы. Мы можем рассматривать теневой DOM как поддерево или как отдельный DOM для элемента. Слово "для элемента" очень важно, поскольку Shadow DOM должен быть привязан к какому-либо из узлов основного DOM. Созданный для любого из элементов Shadow DOM имеет свою уникальную особенность - изолированность, что предоставляет возможным иметь свои собственные стили и скрипты, которые никогда не повлияют на главное дерево. 

Новая объектная модель уже внутри элемента будет называться теневым деревом, корневой узел которого - это Shadow Root, а сам элемент - теневой хост (Shadow Host).

Shadow DOM

Shadow DOM в инспекторе

Вы никогда не задумывались, как в HTML реализованы сложные браузерные компоненты? Например, <input type='range'>.

Взгляните на его код - это чистой воды пример использования Shadow DOM: изолированное теневое дерево со своими стилями и правилами поведения, которое не может повлиять на основной DOM и наоборот.

Отличие Shadow DOM от Virtual DOM

На собеседованиях любят часто задавать этот вопрос, потому что на первый взгляд, теневой DOM действительно может показаться чем-то виртуальным и недоступным. Однако различие в концепции и в том, для чего каждый из них предназначен.

Представьте, что нам необходимо с помощью скрипта поменять в одном из блоков текст, а в другом заменить его дочерний элемент. Так вот, вне зависимости от того, задеваются ли при этом остальные узлы основного DOM, каждый его элемент в результате этой операции перерисуется. Основной механизм действия Virtual DOM - создание копии реального DOM и сохранение его в памяти для того, чтобы далее с помощью специального алгоритма сравнения этих двух деревьев при изменении любого из узлов дерева обновлять и перерисовывать только эту часть. Другими словами, если мы теперь скриптом изменим любой элемент, это изменение применится к виртуальному дереву, после чего новый Virtual DOM будет сравнен с основным скопированным DOM и браузер узнает, что конкретно нужно будет перерисовать.

Virtual DOM

Единственное, что объединяет Shadow DOM и Virtual DOM - это то, что они помогают решать проблемы производительности, создавая отдельный экземпляр DOM. В остальном различие кардинальное: Virtual DOM создает копию всего объекта DOM, а Shadow DOM создает небольшие части объекта DOM, которые имеют собственную изолированную область действия для представляемого ими элемента.

Пример использования Shadow DOM

Поскольку кастомизация готовых браузерных компонентов, имеющих Shadow DOM, слишком ограничена или вовсе невозможна, для определенных задач будет полезным создание собственного. Возможно это сделать двумя способами: зарегистрировать свой собственный компонент или сделать хостом существующий.

Создание собственного компонента

Создадим элемент с теневым деревом, задачей которого будет показать нам приветственную надпись. Управлять именем в тексте элемента будем с помощью атрибута.

Рассмотрим в подробностях скрипт этой задачи:

customElements.define('hello-text', class extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({mode: 'open'});
    shadow.innerHTML = `
    <style>
      p {font: bold 20px sans-serif}
    </style>
    <p>
      Привет! Меня зовут ${this.getAttribute('name')}.
    </p>`;
  }
});
  • customElements - это глобальный объект в JavaScript, который позволяет использовать пользовательские элементы в HTML. Пользовательский элемент в нашем случае - это созданный нами свой тег с теневым деревом, который мы зарегистрируем и сможем использовать в разметке.
  • customElements.define - с помощью метода define мы как раз регистрируем новый пользовательский элемент.
  • hello-text - это имя нашего нового тега.
  • class extends HTMLElement {...} - определение класса, который наследуется от встроенного элемента HTMLElement. Это означает, что новый класс будет иметь все свойства и методы обычного HTML-элемента, плюс те, которые мы можем добавить в этом классе.
  • connectedCallback() {...} - это специальный метод, который вызывается каждый раз, когда элемент добавляется в документ. Это место, где можно выполнять инициализацию элемента.
  • const shadow = this.attachShadow({mode: 'open'}) - этот код создает теневой корень для элемента. Параметр {mode: 'open'} означает, что мы можем получить доступ к Shadow DOM через свойство shadowRoot элемента.
  • shadow.innerHTML - внутреннее содержимое теневого DOM. В нашем случае это будет параграф с приветственным текстом и стили для него.
  • this.getAttribute('name') - в это место будет вставлено значение атрибута name нашего нового тега.

Все, что нам останется сделать в HTML - это вставить уже зарегистрированный нами тег с нужным атрибутом:

<hello-text name="Петя"></hello-text>

Вот, как это будет выглядеть вживую:

Обратите внимание на код этого элемента в инспекторе.

hello-text компонент

Создание теневого хоста из существующего элемента

Глобально задачу оставляем той же, однако за основу возьмем элемент разметки <div id='hello-text'>.

В скрипте теперь достаточно прописать:

const helloTextElement = document.getElementById('hello-text');
helloTextElement.attachShadow({mode: 'open'});
helloTextElement.shadowRoot.innerHTML = `
  <style>
    p {font: bold 20px sans-serif}
  </style>
  <p>
    Привет! Меня зовут ${helloTextElement.getAttribute('name')}.
  </p>`;

Преимущества и недостатки использования Shadow DOM

Инкапсуляция

Одним из главных преимуществ Shadow DOM является то, что он позволяет разработчикам инкапсулировать стили и поведение настраиваемого элемента, предотвращая его влияние на стили и скрипты за пределами его дерева DOM. Это упрощает поддержку и повторное использование настраиваемых элементов в разных частях веб-приложения.

Улучшенная стилизация

С Shadow DOM разработчики могут использовать CSS для стилизации своих настраиваемых элементов, не влияя на остальную часть документа. Это означает, что они могут использовать имена классов и ID, которые конфликтовали бы с остальной частью документа.

Повышенная производительность

Shadow DOM также может повысить производительность веб-приложений за счет сокращения объема CSS и JavaScript, которые необходимо загрузить и выполнить. Это связано с тем, что стили и скрипты можно определить в дереве Shadow DOM, а не в основном дереве документа.

Сложность использования

Shadow DOM - достаточно сложная технология и может потребовать глубокого понимания веб-платформы и JavaScript для эффективного применения. Это может затруднить использование его в веб-приложениях для менее опытных разработчиков.

Поддержка браузерами

Еще одним потенциальным недостатком Shadow DOM является то, что он не поддерживается устаревшими браузерами. Это может ограничить охват веб-приложений, которые полагаются на Shadow DOM, и может потребовать от разработчиков использовать резервные или альтернативные решения для таких браузеров.

Поддержка браузерами
chrome
Chrome
53
firefox
Firefox
63
edge
Edge
79
safari
Safari
10
opera
Opera
40