Виртуальный DOM и детали его реализации в React
Виртуальный DOM (VDOM) — это концепция программирования, в которой идеальное или «виртуальное» представление пользовательского интерфейса хранится в памяти и синхронизируется с «настоящим» DOM при помощи библиотеки, такой как ReactDOM. Этот процесс называется согласованием.
Такой подход и делает API React декларативным: вы указываете, в каком состоянии должен находиться пользовательский интерфейс, а React добивается, чтобы DOM соответствовал этому состоянию. Это абстрагирует манипуляции с атрибутами, обработку событий и ручное обновление DOM, которые в противном случае пришлось бы использовать при разработке приложения.
Поскольку «виртуальный DOM» — это скорее паттерн, чем конкретная технология, этим термином иногда обозначают разные понятия. В мире React «виртуальный DOM» обычно ассоциируется с React-элементами , поскольку они являются объектами, представляющими пользовательский интерфейс. Тем не менее, React также использует внутренние объекты, называемые «волокнами» (fibers), чтобы хранить дополнительную информацию о дереве компонентов. Их также можно считать частью реализации «виртуального DOM» в React.
Теневой DOM похож на виртуальный DOM?
Нет, они совсем разные. Теневой DOM (Shadow DOM) — это браузерная технология, предназначенная в основном для определения области видимости переменных и CSS в веб-компонентах. Виртуальный DOM — это концепция, реализованная библиотеками в JavaScript поверх API браузера.
Что такое «React Fiber»?
Fiber — новый механизм согласования в React 16, основная цель которого сделать рендеринг виртуального DOM инкрементным. Узнать больше об этом.
Shadow DOM
Теневой DOM («Shadow DOM») используется для инкапсуляции. Благодаря ему в компоненте есть собственное «теневое» DOM-дерево, к которому нельзя просто так обратиться из главного документа, у него могут быть изолированные CSS-правила и т.д.
Встроенный теневой DOM
Задумывались ли вы о том, как устроены и стилизованы сложные браузерные элементы управления?
Браузер рисует их своими силами и по своему усмотрению. Их DOM-структура обычно нам не видна, но в инструментах разработчика можно её посмотреть. К примеру, в Chrome для этого нужно активировать пункт «Show user agent shadow DOM».
После этого выглядит так:
То, что находится под #shadow-root – и называется «shadow DOM» (теневой DOM).
Мы не можем получить доступ к теневому DOM встроенных элементов с помощью обычных JavaScript-вызовов или с помощью селекторов. Это не просто обычные потомки, это мощное средство инкапсуляции.
В примере выше можно увидеть полезный атрибут pseudo . Он нестандартный и существует по историческим причинам. С его помощью можно стилизовать подэлементы через CSS, например, так:
/* делаем цвет шкалы ползунка красным */ input::-webkit-slider-runnable-track
Ещё раз заметим, что pseudo – нестандартный атрибут. Если говорить хронологически, то сначала браузеры начали экспериментировать с инкапсуляцией внутренних DOM-структур для элементов, а уже потом, через некоторое время, появился стандарт Shadow DOM, который позволяет делать то же самое нам, разработчикам.
Далее мы воспользуемся современным стандартом Shadow DOM, описанным в спецификации DOM spec и других спецификациях.
Теневое дерево
Каждый DOM-элемент может иметь 2 типа поддеревьев DOM:
- Light tree – обычное, «светлое», DOM-поддерево, состоящее из HTML-потомков. Все поддеревья, о которых мы говорили в предыдущих главах, были «light».
- Shadow tree – скрытое, «теневое», DOM-поддерево, не отражённое в HTML, скрытое от посторонних глаз.
Если у элемента имеются оба поддерева, браузер отрисовывает только теневое дерево. Также мы всё же можем задать «композицию» теневого и обычного деревьев. Позже в главе Слоты теневого DOM, композиция мы рассмотрим детали.
Теневое дерево можно использовать в пользовательских элементах (Custom Elements), чтобы спрятать внутренности компонента и применить к ним локальные стили.
Например, этот элемент прячет свой внутренний DOM в теневом дереве:
А вот как получившийся DOM выглядит в инструментах разработчика в Chrome, весь контент внутри «#shadow-root»:
Итак, вызов elem.attachShadow() создаёт теневое дерево.
Есть два ограничения:
- Для каждого элемента мы можем создать только один shadow root.
- В качестве elem может быть использован пользовательский элемент (Custom Element), либо один из следующих элементов: «article», «aside», «blockquote», «body», «div», «footer», «h1…h6», «header», «main», «nav», «p», «section» или «span». Остальные, например,
, не могут содержать теневое дерево.
Свойство mode задаёт уровень инкапсуляции. У него может быть только два значения:
- «open» – корень теневого дерева («shadow root») доступен как elem.shadowRoot . Любой код может получить теневое дерево elem .
- «closed» – elem.shadowRoot всегда возвращает null . До теневого DOM в таком случае мы сможем добраться только по ссылке, которую возвращает attachShadow (и, скорее всего, она будет спрятана внутри класса). Встроенные браузерные теневые деревья, такие как у , закрыты. До них не добраться.
С возвращаемым методом attachShadow объектом корнем теневого дерева, можно работать как с обычным DOM-элементом: менять его innerHTML или использовать методы DOM, такие как append , чтобы заполнить его.
Элемент с корнем теневого дерева называется – «хозяин» (host) теневого дерева, и он доступен в качестве свойства host у shadow root:
// при условии, что , иначе elem.shadowRoot равен null alert(elem.shadowRoot.host === elem); // true
Инкапсуляция
Теневой DOM отделён от главного документа:
- Элементы теневого DOM не видны из обычного DOM через querySelector . В частности, элементы теневого DOM могут иметь такие же идентификаторы, как у элементов в обычном DOM (light DOM). Они должны быть уникальными только внутри теневого дерева.
- У теневого DOM свои стили. Стили из внешнего DOM не применятся.
/* стили документа не применятся в теневом дереве внутри #elem (1) */ p ); // у теневого дерева свои стили (2) elem.shadowRoot.innerHTML = ` p Hello, John!
`; // виден только запросам внутри теневого дерева (3) alert(document.querySelectorAll('p').length); // 0 alert(elem.shadowRoot.querySelectorAll('p').length); // 1
- Стили главного документа не влияют на теневое дерево.
- …Но свои внутренние стили работают.
- Чтобы добраться до элементов в теневом дереве, нам нужно искать их изнутри самого дерева.
Ссылки
- DOM: https://dom.spec.whatwg.org/#shadow-trees
- Совместимость: https://caniuse.com/#feat=shadowdomv1
- Теневой DOM упоминается во многих других спецификациях, например DOM Parsing указывает, что у shadow root есть innerHTML .
Итого
Теневой DOM – это способ создать свой, изолированный, DOM для компонента.
- shadowRoot = elem.attachShadow() – создаёт теневой DOM для elem . Если mode=»open» , он доступен через свойство elem.shadowRoot .
- Мы можем создать подэлементы внутри shadowRoot с помощью innerHTML или других методов DOM.
Элементы теневого DOM:
- Обладают собственной областью видимости идентификаторов
- Невидимы JavaScript селекторам из главного документа, таким как querySelector ,
- Стилизуются своими стилями из теневого дерева, не из главного документа.
Теневой DOM, если имеется, отрисовывается браузером вместо обычных потомков (light DOM). В главе Слоты теневого DOM, композиция мы разберём, как делать их композицию.
Про Shadow DOM
Продолжаю свой цикл публикаций о группе стандартов Web Components. Моя цель — сформировать реалистичные ожидания от данного набора технологий, а также, вместе с вами, прийти к более четкому пониманию того, где их не стоит применять, и где, напротив, ничего лучше еще не придумано. На этот раз, предлагаю подробнее остановится на Shadow DOM.
Начнем с азов, чтобы те из нас, кто пока не сталкивался с предметом обсуждения, не потеряли интерес к основной части статьи. Итак, Shadow DOM — это часть современного DOM API, которая позволяет создавать изолированные участки документа, со своей собственной внутренней разметкой и своими собственными стилями. Если вы откроете, с помощью инструментов разработчика в браузере, структуру документа, содержащего, к примеру, стандартный HTML-элемент audio — вы увидите в его составе область, обозначенную как #shadow-root и какую-то разметку внутри. Это и есть Shadow DOM элемента audio. Как видите, мы можем столкнуться с «теневыми» участками документов в приложениях и на страницах сайтов, даже если при их создании никак не использовались веб-компоненты или основанные на них библиотеки. Это утверждение справедливо для всех стандартных браузерных UI-примитивов, таких как кнопки, селекты, инпуты и т. д. Хорошая новость в том, что теперь у нас есть возможность создавать собственные универсальные элементы, подобные встроенным браузерным. И Shadow DOM, в данном случае, это ответ на вопрос «как?».
Какие основные вопросы решает Shadow DOM?
- Инкапсуляция. Внутри Shadow DOM создается отдельный «поддокумент», к которому можно применять свои стили, экранированные от воздействий внешней среды (вам не нужно писать многоэтажные имена классов, чтобы обезопасить ваш элемент или внешний документ от «протечек») и где создается свой контекст для методов DOM API, где, к примеру, с помощью селекторов можно получить только те элементы которые находятся внутри и остаются почти невидимыми снаружи (при этом, допустимо использование одинаковых ID у элементов в разных контекстах, без опасности все поломать).
- Композиция. Shadow DOM дает вам контроль над «размещением» непосредственных потомков сложного DOM-элемента в нужных местах его внутренней разметки. В этом случае, Shadow DOM выступает в роли некоего шаблона, в котором можно размещать содержимое в местах, обозначенных специальным тегом — slot. Это дает возможность, к примеру, создавать элементы-лейауты и повторно использовать их.
У тех, кто уже имел дело с библиотеками, основанными на веб-компонентах (такими как LitElement), могло сформироваться ложное впечатление о том, что Shadow DOM — это неотъемлемая часть любого веб-компонента. Это не так. Вы можете создавать компоненты с помощью стандарта Custom Elements и НЕ использовать Shadow DOM при этом, и напротив, создавать теневой DOM у обычных элементов, таких как старый добрый div. Теневой DOM открывает довольно интересные и нетривиальные возможности, например, когда вы динамически добавляете CSS-свойства к элементу через интерфейс «element.style», у вас нет возможности определить псевдоклассы и псеводоэлементы, а также, использовать media-запросы или создавать ключи анимации. Это большой недостаток модели работы со стилями через JavaScript в современных браузерах (работа в этом направлении ведется, но это отдельная сложная тема). Все меняет Shadow DOM:
let myElement = document.createElement('div'); myElement.attachShadow(< mode: 'open', >); myElement.shadowRoot.innerHTML = /*html*/ ` :host < padding: 10px; >:host(:hover) `;
Теперь у нашего div есть реакция на наведение мыши при том, что мы не создавали для этого никаких классов и не вносили никаких изменений во внешние стили. Shadow DOM дает нам доступ к своему элементу-контейнеру через селектор :host, и используя этот селектор, мы можем создавать любые сложные стили для элемента в JS. Прошу принять во внимание, что код приведенный выше, написан исключительно для демонстрации самого принципа, в бою все может выглядеть немного иначе.
Когда стоит применять Shadow DOM?
Думаю, основными областями применения можно считать те-же, что и для веб-компонентов в целом: для создания встраиваемых виджетов и UI-библиотек-агностиков, минимально зависимых от конкретных мета-платформ, экосистем и фреймворков. Везде, где важна инкапсуляция, возможность спрятать «под ковер» реализацию и удобная независимая композиция.
Модные микрофронтенды также являются интересной областью для применения возможностей Shadow DOM.
Также, можно рассмотреть вариант применения в случаях, когда вам необходимо внедрить свое локальное решение в большой и неповоротливый проект с кучей легаси-кода, в котором можно закопаться неоправданно надолго и есть опасность сломать что-то неочевидное: изоляция участка может оказаться быстрым и эффективным решением.
Вялотекущий рефакторинг сложной системы тоже можно проводить через создание «островков безопасности».
Какие могут возникнуть сложности?
Следует понимать, что Shadow DOM в отдельности, НЕ решает вопрос контроля жизненного цикла ваших компонентов и инициализации компонентов во внешней среде (помните, для этого есть Custom Elements).
Shadow DOM в документе может быть создан только через JavaScript, а потому, вы не сможете напрямую использовать предварительный рендер (SSR) для внутренней разметки. Данное ограничение можно обойти, но это отдельный непростой разговор.
В случае, если на сайте используется CSP (Content Security Policy) — вы будете ограничены в выборе способов добавления стилей для элементов внутри теневого DOM. Любая попытка парсить стили из строки вызовет ошибку. Не будет работать ни innerHTML, ни insertRule, ничто иное в этом роде. Самое простое и быстрое решение, но, на мой взгляд, наименее красивое — CSP-флаг unsafe-inline. Если вы создаете виджет для интеграции его на сторонний сайт, рекомендовать пользователям использование небезопасных настроек — это не комильфо. Для браузеров на основе Chromium, выходом может быть использование adoptedStylesheets. Более универсальными решениями будет создание динамических стилей через element.style (что, как писалось выше, имеет свои ограничения), либо добавление в Shadow DOM внешнего файла стилей:
let myElement = document.createElement('div'); myElement.attachShadow(< mode: 'open', >); myElement.shadowRoot.innerHTML = /*html*/ ` `;
На создание Shadow DOM, при прочих равных условиях, уходят дополнительные ресурсы, поэтому, если для вас важен вопрос производительности, старайтесь не использовать теневые участки DOM бездумно повсюду. Часто для создания виджета достаточно всего одного общего теневого DOM, без лишней вложенности и сопутствующих этому дополнительных расходов.
Ну и, поскольку данный вопрос все еще всплывает в обсуждениях, затрону и его: если среди требований к вашему проекту имеется поддержка IE — теневой DOM не ваш выбор. Радует, что все большее число разработчиков отказывается от поддержки некробраузеров, надеюсь скоро не нужно будет вообще об этом вспоминать.
Вывод
Shadow DOM — мощная и гибкая технология. Ее использование может существенно облегчить решение многих задач и открывает простор для творчества в решении задач нетипичных. Но не ждите от нее волшебного ответа на все свои вопросы и полного отсутствия сложностей.
Теневая модель документа (Shadow DOM)
Веб-компоненты — это набор новых стандартов, которые:
- Позволяют создавать виджеты,
- … которые могут быть использованы повторно без риска сбоев
- … и которые не приводят к разрушению страницы, если в следующей версии соответствующего веб-компонента внесены изменения в детали реализации.
Значит ли это, что придётся определиться, где стоит использовать HTML/JavaScript, а где — веб-компоненты? Нет! С помощью HTML и JavaScript можно создавать разные визуальные интерактивные штуки. В том числе и виджеты. При разработке виджета целесообразно воспользоваться знаниями HTML и JavaScript. Веб-компоненты придуманы, чтобы вам в этом помочь.
Не вижу смысла в переключении на другую технологию при разработке виджета. Например, я крайне не приветствую разработку виджетов на основе . Этот элемент надёжен (если вы измените то, что он отрисовывает, странице не будет нанесён вред), однако он несовместим с доступностью, индексацией, композицией и независимостью от разрешения.
Тем не менее, есть существенная проблема, из-за которой разрабатывать виджеты на HTML и JavaScript довольно сложно: дерево элементов виджета не инкапсулировано от остальной страницы. Отсутствие инкапсуляции значит, что таблица стилей вашего сайта может быть по ошибке применена к компонентам внутри виджета; ваш JavaScript может случайно изменить составляющие виджета; названия ваших идентификаторов могут оказаться идентичными названиям идентификаторов внутри виджета и так далее.
Наиболее вредоносным обстоятельством отсутствия инкапсуляции является то, что после модернизации библиотеки и изменений в дереве элементов внутри виджета стили и скрипты вашей страницы могут быть разрушены самым непредсказуемым образом.
Веб-компоненты состоят из четырёх частей:
- Шаблоны (Templates)
- Теневая модель документа (Shadow DOM)
- Настраиваемые элементы (Custom Elements)
- Пакетирование (Packaging)
Теневая модель документа направлена на решение проблемы инкапсуляции дерева элементов. Четыре части веб-компонентов были придуманы для того, чтобы использоваться вместе, однако их можно использовать и отдельно. В этом руководстве вы узнаете, как использовать теневую модель документа (Shadow DOM).
Спецификация теневой модели документа пока поддерживается только в Chrome 25, поэтому для этого API используется вендорный префикс webkit.
Здравствуй, мир теней
В спецификации теневой модели документа вводится новый тип элементов. Он называется корневым элементом теневого дерева (shadow root). Элемент, к которому привязан корневой элемент теневого дерева, называется ведущим элементом теневого дерева (shadow host). Содержимое ведущего элемента не отображается, вместо него отображается содержимое корневого элемента теневого дерева.
Например, если у вас такая разметка:
button>Здравствуй, мир! button> script> var host = document.querySelector('button'); var root = host.webkitCreateShadowRoot(); root.textContent = 'こんにちは、影の世界!'; script>
на вашей странице будет отображено
Это ещё не всё, если JavaScript страницы запросит textContent кнопки, ответом будет не «こんにちは、影の世界!», а «Здравствуй, мир!», потому что дочернее дерево инкапсулировано.
Пожалуй, здесь нарушено одно золотое правило: в теневое дерево нельзя помещать контент. Он должен быть помещён в основное дерево страницы, чтобы быть доступным скринридерам, поисковым системам и т.д. Теневое дерево предназначено для всей той семантически бессмысленной разметки, которая нужна, чтобы создать красивый и удобный виджет. А вот контент должен оставаться в коде страницы.
Конечно же, за нарушение этого правила вас никто не осудит; в сети вы свободны делать более-менее всё, что вам угодно. Однако, не перегибайте палку.
Разделение контента и представления
Теперь мы посмотрим как, используя теневую модель документа, можно разделить контент и представление. Скажем, у нас есть вот такая именная табличка:
Привет! Меня зовут
Вот разметка. Такую вы бы написали сейчас. В ней не используется теневая модель документа.
style> .outer < border: 2px solid brown; border-radius: 1em; background: red; font-size: 20pt; width: 14em; height: 7em; text-align: center; > .boilerplate < color: white; font-family: sans-serif; padding: 0.5em; > .name < color: black; background: white; font-family: "Marker Felt", cursive; font-size: 45pt; padding-top: 0.2em; > style> div class="outer"> div class="boilerplate"> Здравствуйте! Меня зовут div> div class="name"> Сергей div> div>
Так как дерево элементов не инкапсулировано, всё содержимое именной таблички помещено прямо в код страницы. Если волей случая для каких-либо элементов в остальном коде страницы используются те же имена классов для стилизации или подключения скрипта, нам придётся не сладко.
Этого можно избежать.
Шаг 1. Прячем описание представления
С точки зрения семантики нам нужны только следующие сведения:
- Перед нами именная табличка.
- На табличке имя «Сергей».
Сначала напишем разметку с учётом наших семантических требований:
div id="nameTag">Сергей div>
Затем поместим все теги div и описание стилей, отвечающих за представление, в элемент :
div id="nameTag">Сергей div> template id="nameTagTemplate"> style> .outer < border: 2px solid brown; … то же что и выше … style> div class="outer"> div class="boilerplate"> Здравствуйте! Меня зовут div> div class="name"> Сергей div> div> template>
На данном этапе отображается только «Сергей». Так как мы поместили элементы, отвечающие за представление, в элемент , они не отображаются, однако их можно извлечь с помощью JavaScript. Что мы сейчас и сделаем, чтобы заполнить корневой элемент теневого дерева:
script> var shadow = document.querySelector('#nameTag').webkitCreateShadowRoot(); var template = document.querySelector('#nameTagTemplate'); shadow.appendChild(template.content); template.remove(); script>
Стандарты для создания шаблонов, вроде теневой модели документа, находятся на стадии становления. Элемент поддерживается в Chrome Canary. Корневой элемент теневого дерева можно заполнить также с помощью таких хорошо известных свойств и методов как innerHTML , appendChild , getElementById и т.д. Эта статья посвящена теневой модели документа, так что мы не будем вникать в работу элемента глубже. Если вы хотите узнать о нём больше, прочитайте статью «Новый HTML-тег template ».
Теперь, после прописания кода корневого элемента теневого дерева, именная табличка снова отображается полностью. Если кликнуть по ней правой кнопкой мыши и просмотреть код элемента, можно увидеть симпатичную семантическую разметку:
div id="nameTag">Сергей div>
Как видите, мы спрятали описание представления именной таблички, используя теневое дерево.
Шаг 2. Разделение контента и представления
В коде страницы теперь не видно описания представления именной таблички, однако нельзя говорить о разделении представления и контента. Ведь хоть на странице и присутствует контент (имя «Сергей»), однако на самом деле отображается имя, скопированное в корневой элемент теневого дерева. Чтобы изменить имя на табличке, его нужно поменять в двух местах, что в результате может привести к непоследовательности.
Элементы HTML композиционны — например, можно поместить кнопку в таблицу. Композиционность именно то, что нам в этом случае нужно: именная табличка должна быть композицией из красного фона, текста приветствия и имени на табличке.
Вы, создатель компонента, определяете, какая композиция должна быть у вашего виджета, используя новый элемент . Он создаёт точку вставки в представлении виджета, в которой выборочно отображается контент из ведущего элемента теневого дерева.
Изменим разметку теневого дерева на следующую:
template id="nameTagTemplate"> style> … style> div class="outer"> div class="boilerplate"> Здравствуйте! Меня зовут div> div class="name"> content> content> div> div> template>
При отображении именной таблички, содержимое из ведущего элемента проецируется в то место, где находится элемент .
Теперь структура страницы упрощена, ведь имя указано только в одном месте — в коде страницы. Если вам когда-нибудь потребуется обновить имя пользователя, нужно только прописать:
document.querySelector('#nameTag').textContent = 'Игорь';
и всё. Отображение именной таблички автоматически обновляется браузером, потому что происходит проецирование её содержимого туда, где расположен .
Пример использования Shadow DOM:
Новое имя: Обновить
Привет! Меня зовут
Теперь мы добились разделения контента и представления. Контент помещён в дерево страницы; представление — в теневое дерево. Когда приходит время что-либо отобразить, браузер их синхронизирует автоматически.
Шаг 3. Польза
Разделяя контент и представление можно упростить код, используемый для управления контентом. В примере с именной табличкой в этом коде простая конструкция с всего одним вместо нескольких.
Если нам нужно изменить представление, код в дереве страницы менять не нужно!
Например, представим что нам нужно локализировать именную табличку. Мы имеем дело с той же самой именной табличкой, так что семантическое содержимое страницы не меняется:
div id="nameTag">Сергей div>
Исходный код в корневом элементе теневого дерева остаётся прежним. Изменяется только то, что в него подставляется:
template id="nameTagTemplate"> style> .outer < border: 2px solid pink; border-radius: 1em; background: url(sakura.jpg); font-size: 20pt; width: 12em; height: 7em; text-align: center; font-family: sans-serif; font-weight: bold; > .name < font-size: 45pt; font-weight: normal; margin-top: 0.8em; padding-top: 0.2em; > style> div class="outer"> div class="name"> content> content> div> と申します。 div> template>
Теперь мы получили именную табличку с текстом на японском языке:
Новое имя: Обновить
Автор фонового изображения — Майк Дауман (Mike Dowman); изображение использовано в согласии с условиями лицензии Creative Commons.
Это значительный шаг вперёд, ведь код для обновления имени зависит от структуры соответствующего компонента, которая теперь проста и последовательна. Ваш код для обновления имени не должен учитывать элементы, которые используются для отображения контента. Если взглянуть на то, что отображается, в русском языке имя идёт на втором месте (после «Здравствуйте! Меня зовут»), но первым в японском (перед «と申します»). Это различие не имеет никакого семантического значения на этапе обновления отображаемого имени, так что не должно быть необходимости учитывать этот нюанс в коде обновления имени.
Бонус: продвинутое проецирование
В примере выше элемент выбирает контент из ведущего элемента теневого дерева. Используя атрибут select можно контролировать что проецирует элемент . Также можно использовать несколько элементов .
Например, у нас есть такой код страницы:
div id="nameTag"> div class="first">Сергей div> div>С. Максимов div> div class="email">serg@ div> div>
и корневой элемент теневого дерева, который с помощью селекторов CSS проводит выборку определённого контента:
div style="background: purple; padding: 1em;"> div style="color: red;"> content select=".first"> content> div> div style="color: yellow;"> content select="div"> content> div> div style="color: blue;"> content select=".email"> content> div> div>
Примечание: с помощью select можно выбрать только те элементы, которые являются непосредственно дочерними по отношению к главному узлу. Это значит что нельзя выбрать непрямой потомок (например select=»table tr» ).
Элемент подходит под условие и . Сколько раз будет отображаться электронный адрес Сергея и в каком цвете?
C. Максимов
Ответ: электронный адрес Сергея выводится один раз, в желтом цвете.
Причиной этому то, что создание дерева для контента, который впоследствии будет отображаться на экране, напоминает организацию большого праздника. Элемент content — это приглашение, которое впускает контент из кода страницы в служебные помещения теневой модели документа для последующего отображения на странице. Эти приглашения доставляются по очереди; приглашение получает тот, кто прописан как получатель (с помощью атрибута select ). Контент получает приглашение и отправляется на праздник. Если на тот же адрес отправлено ещё одно приглашение, то оно никого не застаёт дома, и никто больше не придёт на ваш праздник.
В примере выше подходит под условия наличия div и .email , однако так как элемент content с селектором div в коде идёт первым, отправляется на жёлтый праздник и на синий праздник идти некому.
Если какой-то элемент не приглашён ни на один праздник, он не отображается вовсе. Вот что случилось с текстом «Здравствуй, мир» в самом первом примере. Это может пригодиться, когда нужно достичь кардинально отличающегося отображения: пропишите семантическую модель в коде страницы, которая будет доступна скриптам, но не будет отражаться, и с помощью JavaScript подключите её к абсолютно иной модели отображения в теневом дереве.
Например, в HTML предложена прекрасная возможность выбора даты. Если прописать , получите аккуратный всплывающий календарь. Но что если нужно предоставить пользователю возможность выбрать диапазон дат для отпуска его мечты? Код страницы нужно прописать так:
div >"dateRangePicker"> label for="start">Начало: input type="date" name="startDate" >"start">
label for="end">Конец: input type="date" name="endDate" >"end">