W3docs

Продвинутые техники работы с DOM

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

Освоение продвинутых техник работы с DOM поможет вам строить динамичные, модульные и поддерживаемые интерфейсы на чистом JavaScript — без необходимости использовать фреймворки. В этом руководстве рассматриваются два ключевых инструмента современной разработки компонентов: элемент <template> для объявления переиспользуемой инертной разметки и Shadow DOM для инкапсуляции структуры, стилей и поведения компонента. Вместе они составляют основу, на которой строятся веб-компоненты и пользовательские элементы.

Создание и использование шаблонов

Зачем нужны шаблоны

До появления <template> разработчики создавали переиспользуемую разметку, помещая HTML в скрытые элементы <div>, строковые литералы JavaScript или блоки <script type="text/template">. У каждого подхода есть недостатки: скрытые <div> всё равно требуют от браузера парсинга и загрузки ресурсов (изображения загружаются, скрипты выполняются), а строковые шаблоны лишены подсветки синтаксиса и легко ломаются.

Элемент <template> решает эту проблему. Его содержимое парсится, но остаётся инертным: браузер строит узлы DOM, но не отображает их, не выполняет их скрипты и не загружает изображения или медиафайлы до тех пор, пока вы явно не клонируете содержимое в живой документ. Именно поэтому <template> является правильным инструментом для объявления разметки, которую вы планируете многократно инстанцировать.

Использование элемента <template>

Элемент <template> позволяет определить HTML, который не отображается при загрузке страницы. Доступ к его содержимому осуществляется через свойство content (только для чтения), которое возвращает DocumentFragment.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Using the <template> Element</title>
</head>
<body>
    <template id="my-template">
        <div class="card">
            <h2>Title</h2>
            <p>Content goes here...</p>
        </div>
    </template>
    <button id="show-template">Show Template</button>
    <div id="content"></div>

    <script>
        document.getElementById('show-template').addEventListener('click', () => {
            const template = document.getElementById('my-template');
            const content = document.getElementById('content');
            const clone = template.content.cloneNode(true);
            content.appendChild(clone);
        });
    </script>
</body>
</html>

В этом примере показана базовая структура элемента <template>, содержащего карточку с заголовком и текстом. Содержимое шаблона клонируется и вставляется в DOM при нажатии кнопки. Подробнее об этом элементе рассказано в разделе элемент <template>.

Клонирование и вставка содержимого шаблона

Чтобы переиспользовать <template>, необходимо клонировать его content и вставить клон в DOM. Всегда передавайте true в cloneNode, чтобы скопировать всё поддерево целиком (элемент и всех его потомков) — cloneNode(false) копирует только верхний узел и вернёт пустой фрагмент.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Cloning and Inserting Template Content</title>
</head>
<body>
    <template id="card-template">
        <div class="card">
            <h2 class="title"></h2>
            <p class="body"></p>
        </div>
    </template>

    <button id="add-card">Add Card</button>
    <div id="container"></div>

    <script>
        let count = 0;
        document.getElementById('add-card').addEventListener('click', () => {
            const template = document.getElementById('card-template');
            const clone = template.content.cloneNode(true);

            // Fill the clone with dynamic data before inserting it.
            count++;
            clone.querySelector('.title').textContent = 'Card ' + count;
            clone.querySelector('.body').textContent = 'Created at ' + new Date().toLocaleTimeString();

            document.getElementById('container').appendChild(clone);
        });
    </script>
</body>
</html>

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

  • DocumentFragment очищается при добавлении в DOM. После appendChild(clone) дочерние элементы фрагмента перемещаются в контейнер, а сам фрагмент остаётся пустым — поэтому вызывайте cloneNode по одному разу для каждого добавляемого элемента.
  • Выполняйте запросы к клону, а не к документу. Селекторы вида clone.querySelector('.title') работают с фрагментом, который ещё не вставлен в DOM, поэтому вы заполняете его прежде, чем он попадёт в живой документ (что позволяет избежать лишних перерасчётов макета). Подробнее см. в разделе поиск с помощью querySelector.
  • document.importNode(template.content, true) — это кросс-документный эквивалент; используйте его, когда шаблон находится в другом документе или iframe, чтобы импортированные узлы принадлежали текущему документу.

Shadow DOM

Введение в Shadow DOM

Shadow DOM — это веб-стандарт, обеспечивающий инкапсуляцию в веб-компонентах. Он прикрепляет к элементу (называемому shadow host) отдельное скрытое дерево DOM — shadow tree. Узлы этого дерева недоступны обычному методу document.querySelector страницы, а стили, определённые внутри него, не проникают в остальную часть страницы. Это позволяет изолировать внутреннюю структуру, стили и поведение компонента от глобального документа.

Несколько терминов, которые будут встречаться в тексте:

  • Shadow host — обычный элемент, к которому прикреплено shadow tree.
  • Shadow root — корневой узел shadow tree, возвращаемый методом attachShadow().
  • Shadow boundary — граница между shadow tree и остальной частью документа, которую область видимости не пересекает.

Режимы open и closed

Метод attachShadow() требует указания параметра mode:

const open = host.attachShadow({ mode: 'open' });
// host.shadowRoot  →  the shadow root (accessible from outside)

const closed = host2.attachShadow({ mode: 'closed' });
// host2.shadowRoot →  null (the root is hidden from outside scripts)

На практике предпочтительнее использовать open. Режим closed не обеспечивает реальной безопасности — кто угодно может переопределить attachShadow до выполнения вашего кода — и лишь усложняет тестирование и отладку компонента.

Инкапсуляция и компонентная разработка

Инкапсуляция гарантирует, что стили и скрипты, определённые внутри компонента, не вытекают наружу и не влияют на остальную часть документа — и что внешние стили не проникают внутрь. В примере ниже к элементу прикрепляется shadow root, а затем его содержимое формируется в DocumentFragment, чтобы всё поддерево было вставлено за одну операцию. Элемент <slot> проецирует существующее («light DOM») содержимое хоста в shadow tree вместе с собственной разметкой компонента.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Shadow DOM Example</title>
    <style>
        .card {
            padding: 20px;
            margin: 10px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <div id="shadow-host" class="card">
        <span>This is the light DOM content</span>
    </div>

    <script>
        const host = document.getElementById('shadow-host');
        const shadowRoot = host.attachShadow({ mode: 'open' });

        const fragment = document.createDocumentFragment();
        const style = document.createElement('style');
        style.textContent = `.shadow-card { padding: 20px; margin: 10px; border: 1px solid blue; color: blue; }`;
        const slot = document.createElement('slot');
        const card = document.createElement('div');
        card.className = 'shadow-card';
        card.textContent = 'This is inside the Shadow DOM';

        fragment.appendChild(style);
        fragment.appendChild(slot);
        fragment.appendChild(card);
        shadowRoot.appendChild(fragment);
    </script>
</body>
</html>

В этом примере на элементе #shadow-host создаётся shadow tree, в которое внедряются стили и содержимое. Light DOM содержимое (This is the light DOM content) остаётся в хосте и отображается внутри shadow tree через элемент <slot>, появляясь рядом с shadow-содержимым, а не заменяясь им.

Обратите внимание, что инкапсуляция влияет на стили следующим образом. Правило .shadow-card находится внутри shadow tree и применяется только к узлам этого дерева; оно не может совпасть с .card на остальной странице, а правило .card страницы не может проникнуть в shadow tree. Исключением являются наследуемые свойстваcolor, font-family, line-height и подобные — они по-прежнему передаются через границу в slotted-содержимое. Инкапсуляция блокирует совпадение селекторов, но не наследование. Подробнее см. в разделах стилизация Shadow DOM и слоты и композиция.

Когда использовать каждую технику

  • Используйте <template>, когда нужно многократно инстанцировать одну и ту же разметку (строки списков, карточки, модальные окна) и хочется объявить её декларативно в HTML.
  • Используйте Shadow DOM, когда виджет требует стилей, которые не должны конфликтовать со стилями страницы-хоста — кнопка системы дизайна, выбор даты, встраиваемый виджет.
  • Комбинируйте оба подхода — определяйте разметку в <template> и клонируйте её в shadow root — чтобы строить полноценные переиспользуемые пользовательские элементы.

Лучшие практики

  • Предпочитайте DocumentFragment для пакетных вставок: добавление фрагмента в shadow root (или любой другой контейнер) за одну операцию минимизирует перерасчёты макета и улучшает производительность отрисовки.
  • Заполняйте клоны перед вставкой: выполняйте запросы и заполняйте клонированный фрагмент, пока он ещё не прикреплён к DOM — это позволит браузеру произвести только один перерасчёт макета при добавлении.
  • Выбирайте режим shadow open: это сохраняет возможность отладки и тестирования компонентов; режим closed не обеспечивает реальной безопасности.
  • Используйте document.importNode() при работе с разными документами: при клонировании содержимого из другого документа или iframe метод importNode обеспечивает корректное владение узлами и предотвращает кросс-документные ошибки.
  • Минимизируйте light DOM: используйте элементы <slot> для проецирования только того содержимого, которое действительно принадлежит странице, сохраняя хост предсказуемым.
Информация

Используйте Shadow DOM для инкапсуляции стилей и функциональности внутри компонентов, предотвращая конфликты стилей и обеспечивая модульный, поддерживаемый код.

Заключение

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

Практика

Практика
Какие из следующих утверждений о продвинутых техниках DOM верны?
Какие из следующих утверждений о продвинутых техниках DOM верны?
Was this page helpful?