Skip to content

Latest commit

 

History

History
175 lines (119 loc) · 12.4 KB

File metadata and controls

175 lines (119 loc) · 12.4 KB

Shadow DOM

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

Shadow DOM -- это внутренний DOM элемента, который существует отдельно от внешнего документа. В нём могут быть свои ID, свои стили и так далее. Причём снаружи его, без применения специальных техник, не видно, поэтому не возникает конфликтов. [cut]

Внутри браузера

Концепция Shadow DOM начала применяться довольно давно внутри самих браузеров. Когда браузер показывает сложные элементы управления, наподобие слайдера <input type="range"> или календаря <input type="date"> -- внутри себя он конструирует их из самых обычных стилизованных <div>, <span> и так далее.

С первого взгляда они незаметны, но если в настройках Chrome Development Tools выбрать показ Shadow DOM, то их можно легко увидеть.

Например, вот такое содержимое будет у <input type="date">:

То, что находится под #shadow-root -- это и есть Shadow DOM.

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

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

<!--+ run  no-beautify -->
<style>
*!*
input::-webkit-datetime-edit {
*/!*
  background: red;
}
</style>

<input type="date">

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

Далее мы рассмотрим работу с Shadow DOM из JavaScript, по стандарту Shadow DOM.

Создание Shadow DOM

Shadow DOM можно создать внутри любого элемента вызовом elem.createShadowRoot().

Например:

<!--+ run autorun="no-epub" -->
<p id="elem">Доброе утро, страна!</p>

<script>
  var root = elem.createShadowRoot();
  root.innerHTML = "<p>Привет из подполья!</p>";
</script>

Если вы запустите этот пример, то увидите, что изначальное содержимое элемента куда-то исчезло и показывается только "Привет из подполья!". Это потому, что у элемента есть Shadow DOM.

С момента создания Shadow DOM обычное содержимое (дети) элемента не отображается, а показывается только Shadow DOM.

Внутрь этого Shadow DOM, при желании, можно поместить обычное содержимое. Для этого нужно указать, куда. В Shadow DOM это делается через "точку вставки" (insertion point). Она объявляется при помощи тега <content>, например:

<!--+ run autorun="no-epub" -->
<p id="elem">Доброе утро, страна!</p>

<script>
  var root = elem.createShadowRoot();
  root.innerHTML = "<h3>*!*<content></content>*/!*</h3> <p>Привет из подполья!</p>";
</script>

Теперь вы увидите две строчки: "Доброе утро, страна!" в заголовке, а затем "Привет из подполья".

Shadow DOM примера выше в инструментах разработки:

Важные детали:

  • Тег `` влияет только на отображение, он не перемещает узлы физически. Как видно из картинки выше, текстовый узел "Доброе утро, страна!" остался внутри `p#elem`. Его можно даже получить при помощи `elem.firstElementChild`.
  • Внутри `` показывается не элемент целиком `

    `, а его содержимое, то есть в данном случае текст "Доброе утро, страна!".

В <content> атрибутом select можно указать конкретный селектор содержимого, которое нужно переносить. Например, <content select="h3"></content> перенесёт только заголовки.

Внутри Shadow DOM можно использовать <content> много раз с разными значениями select, указывая таким образом, где конкретно какие части исходного содержимого разместить. Но при этом дублирование узлов невозможно. Если узел показан в одном <content>, то в следующем он будет пропущен.

Например, если сначала идёт <content select="h3.title">, а затем <content select="h3">, то в первом <content> будут показаны заголовки <h3> с классом title, а во втором -- все остальные, кроме уже показанных.

В примере выше тег <content></content> внутри пуст. Если в нём указать содержимое, то оно будет показано только в том случае, если узлов для вставки нет. Например потому что ни один узел не подпал под указанный select, или все они уже отображены другими, более ранними <content>.

Например:

<!--+ run autorun="no-epub"  no-beautify -->
  
<section id="elem">
  <h1>Новости</h1>
  <article>Жили-были <i>старик со старухой</i>, но недавно...</article>
</section>

<script>
  var root = elem.createShadowRoot();

  root.innerHTML = "<content select='h1'></content> \
   <content select='.author'>Без автора.</content> \
   <content></content>";
  
</script>

<button onclick="alert(root.innerHTML)">root.innerHTML</button>

При запуске мы увидим, что:

  • Первый `` выведет заголовок.
  • Второй `` вывел бы автора, но так как такого элемента нет -- выводится содержимое самого ``, то есть "Без автора".
  • Третий `` выведет остальное содержимое исходного элемента -- уже без заголовка `

    `, он выведен ранее!

Ещё раз обратим внимание, что <content> физически не перемещает узлы по DOM. Он только показывает, где их отображать, а также, как мы увидим далее, влияет на применение стилей.

Корень shadowRoot

После создания корень внутреннего DOM-дерева доступен как elem.shadowRoot.

Он представляет собой специальный объект, поддерживающий основные методы CSS-запросов и подробно описанный в стандарте как ShadowRoot.

Если нужно работать с содержимым в Shadow DOM, то нужно перейти к нему через elem.shadowRoot. Можно и создать новое Shadow DOM-дерево из JavaScript, например:

<!--+ run autorun="no-epub" -->
<p id="elem">Доброе утро, страна!</p>

<script>
*!*
  // создать новое дерево Shadow DOM для elem
*/!*
  var root = elem.createShadowRoot();

  root.innerHTML = "<h3><content></content></h3> <p>Привет из подполья!</p> <hr>";
</script>

<script>
*!*
  // прочитать данные из Shadow DOM для elem
*/!*
  var root = elem.shadowRoot;
  // Привет из подполья!
  document.write("<p>p:" + root.querySelector('p').innerHTML);
  // пусто, так как физически узлы - вне content 
  document.write("<p>content:" + root.querySelector('content').innerHTML);
</script>

[warn header="Внутрь встроенных элементов так "залезть" нельзя"] На момент написания статьи shadowRoot можно получить только для Shadow DOM, созданного описанным выше способом, но не встроенного, как в элементах типа <input type="date">. [/warn]

Итого

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

  • Ряд браузерных элементов со сложной структурой уже имеют Shadow DOM.
  • Можно создать Shadow DOM внутри любого элемента вызовом `elem.createShadowRoot()`. В дальнейшем его корень будет доступен как `elem.shadowRoot`. У встроенных элементов он недоступен.
  • Как только у элемента появляется Shadow DOM, его изначальное содержимое скрывается. Теперь показывается только Shadow DOM, который может указать, какое содержимое хозяина куда вставлять, при помощи элемента ``. Можно указать селектор `` и размещать разное содержимое в разных местах Shadow DOM.
  • Элемент `` перемещает содержимое исходного элемента в Shadow DOM только визуально, в структуре DOM оно остаётся на тех же местах.

Подробнее спецификация описана по адресу .

Далее мы рассмотрим работу с шаблонами, которые также являются частью платформы Web Components и не заменяют существующие шаблонные системы, но дополняют их важными встроенными в браузер возможностями.