Доступ к элементам Shadow DOM

Haunted — это библиотека для создания веб-компонентов на базе современных возможностей браузера, таких как Custom Elements и Shadow DOM. Она строится на реактивной модели, подобной React Hooks, что позволяет создавать динамичные компоненты с минимальным количеством шаблонного кода. Понимание работы с Shadow DOM критично для корректного взаимодействия компонентов и управления их внутренней структурой.


Создание компонента с Shadow DOM

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

import { html, render, component } from 'haunted';

function MyComponent() {
  return html`
    <style>
      p {
        color: red;
      }
    </style>
    <p>Текст внутри Shadow DOM</p>
  `;
}

customElements.define('my-component', component(MyComponent));
  • component(MyComponent) превращает функцию в полноценный веб-компонент.
  • Shadow DOM создаётся автоматически, если компонент рендерится через Haunted.
  • Внутри Shadow DOM можно использовать <style> для локальных стилей, которые не будут влиять на глобальные элементы.

Доступ к Shadow DOM элементов

Для взаимодействия с внутренними элементами Shadow DOM используются стандартные методы DOM API. Основные из них:

  • shadowRoot.querySelector(selector) — поиск первого совпадения.
  • shadowRoot.querySelectorAll(selector) — поиск всех элементов.
  • shadowRoot.getElementById(id) — поиск элемента по идентификатору.
  • shadowRoot.children и shadowRoot.childNodes — доступ к дочерним узлам.

Пример:

const myElement = document.querySelector('my-component');
const shadowParagraph = myElement.shadowRoot.querySelector('p');
console.log(shadowParagraph.textContent); // "Текст внутри Shadow DOM"

Важно помнить, что прямой доступ к Shadow DOM возможен только если он открыт (mode: 'open'). По умолчанию Haunted создаёт Shadow DOM в открытом режиме.


Использование ref для упрощённого доступа

Haunted поддерживает ref через хук useRef, что облегчает работу с элементами внутри Shadow DOM:

import { html, component } from 'haunted';
import { useRef, useEffect } from 'haunted';

function RefComponent() {
  const paragraphRef = useRef(null);

  useEffect(() => {
    if (paragraphRef.current) {
      paragraphRef.current.textContent = 'Обновлённый текст';
    }
  }, []);

  return html`<p ${paragraphRef}>Исходный текст</p>`;
}

customElements.define('ref-component', component(RefComponent));
  • useRef() создаёт объект с полем current, куда помещается DOM-элемент.
  • useEffect() используется для выполнения действий после рендера.
  • Это позволяет безопасно изменять содержимое или свойства элементов, не нарушая реактивной модели Haunted.

Динамическое создание элементов внутри Shadow DOM

Haunted поддерживает реактивные шаблоны, поэтому добавление новых элементов внутрь Shadow DOM можно выполнять декларативно:

import { html, component } from 'haunted';
import { useState } from 'haunted';

function DynamicList() {
  const [items, setItems] = useState(['Элемент 1', 'Элемент 2']);

  return html`
    <ul>
      ${items.map(item => html`<li>${item}</li>`)}
    </ul>
    <button @click=${() => setItems([...items, `Элемент ${items.length + 1}`])}>
      Добавить
    </button>
  `;
}

customElements.define('dynamic-list', component(DynamicList));
  • Изменения состояния автоматически перерисовывают Shadow DOM.
  • Любые новые элементы сразу становятся частью Shadow DOM компонента.

Ограничения и рекомендации

  1. Инкапсуляция стилей: Стили из глобального CSS не распространяются на Shadow DOM. Для глобального оформления используется ::part() или ::slotted().
  2. Слоты (<slot>): Для передачи контента из внешнего DOM внутрь компонента применяется механизм слотов. Элементы, помещённые через слот, остаются в Light DOM.
  3. Селекторы вне компонента: Не стоит пытаться напрямую изменять элементы внутри Shadow DOM извне — это нарушает инкапсуляцию и делает код менее предсказуемым.
  4. Асинхронные операции: Любые асинхронные изменения состояния должны учитывать, что Shadow DOM уже рендерится и элементы могут отсутствовать в момент обращения.

Работа со слотами и Shadow DOM

Для динамического управления контентом слотов можно использовать API assignedNodes() и assignedElements():

function SlotComponent() {
  return html`
    <slot></slot>
  `;
}

const slotEl = document.querySelector('slot-component').shadowRoot.querySelector('slot');
const assigned = slotEl.assignedElements();
console.log(assigned); // Массив элементов, переданных через слот
  • Позволяет получать и изменять содержимое слотов.
  • Можно фильтровать или манипулировать отдельными элементами без нарушения инкапсуляции Shadow DOM.

Интеграция с внешними библиотеками

Haunted хорошо сочетается с DOM-библиотеками и утилитами вроде Lit, jQuery или даже Vanilla JS. Доступ к Shadow DOM через shadowRoot позволяет использовать привычные методы для поиска и модификации элементов, при этом соблюдая принципы компонентной архитектуры.


Детальное владение Shadow DOM в Haunted обеспечивает:

  • Надёжную инкапсуляцию структуры и стилей.
  • Чёткий и безопасный доступ к внутренним элементам через shadowRoot или ref.
  • Возможность динамического и реактивного обновления контента без нарушения изоляции компонента.