← Назад к вопросам

Для чего нужен теневой DOM в браузере?

2.0 Middle🔥 151 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Shadow DOM: изоляция и инкапсуляция компонентов

Shadow DOM — это мощный API браузера для создания изолированных DOM деревьев внутри элемента. Это ключевая технология для работы с Web Components и создания по-настоящему переиспользуемых компонентов.

Назначение Shadow DOM

1. Изоляция стилей

Одна из главных проблем в больших приложениях — конфликты CSS стилей. Shadow DOM решает эту проблему:

// Создание Shadow DOM
const host = document.getElementById("my-component");
const shadow = host.attachShadow({ mode: "open" });

// Добавление стилей — они не влияют на остальную страницу
shadow.innerHTML = `
  <style>
    h1 { color: blue; }  /* Работает только внутри Shadow DOM */
    p { font-size: 14px; } /* Не конфликтует с глобальными стилями */
  </style>
  <h1>Привет!</h1>
  <p>Это изолированный компонент</p>
`;

Внешние стили не повлияют на компонент:

/* В основном стиле */
h1 {
  color: red;  /* НЕ повлияет на h1 в Shadow DOM */
}

p {
  font-size: 24px; /* НЕ повлияет на p в Shadow DOM */
}

2. Инкапсуляция структуры

Внутренняя структура компонента скрыта от JavaScript на уровне основной страницы:

// На уровне Light DOM
const element = document.querySelector("my-component");
console.log(element.querySelector("h1")); // null!
// Не можем найти элементы внутри Shadow DOM

// Внутри Shadow DOM мы имеем полный доступ
const shadow = element.shadowRoot;
console.log(shadow.querySelector("h1")); // <h1>Привет!</h1>

Создание простого Web Component

// Определение Web Component с Shadow DOM
class MyButton extends HTMLElement {
  constructor() {
    super();
    
    // Создаём Shadow DOM
    const shadow = this.attachShadow({ mode: "open" });
    
    // Добавляем структуру и стили
    shadow.innerHTML = `
      <style>
        :host {
          display: inline-block;
        }
        
        button {
          padding: 10px 20px;
          background-color: #007bff;
          color: white;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          font-size: 14px;
        }
        
        button:hover {
          background-color: #0056b3;
        }
        
        button:active {
          transform: scale(0.95);
        }
      </style>
      <button><slot>Нажми на меня</slot></button>
    `;
  }
  
  connectedCallback() {
    const button = this.shadowRoot.querySelector("button");
    button.addEventListener("click", () => {
      this.dispatchEvent(new CustomEvent("click-custom"));
    });
  }
}

// Регистрируем компонент
customElements.define("my-button", MyButton);
<!-- Использование -->
<my-button>Кликни</my-button>
<my-button>Другая кнопка</my-button>

Slot — распределение контента

Slot позволяет пробросить контент из Light DOM в Shadow DOM:

class MyCard extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: "open" });
    
    shadow.innerHTML = `
      <style>
        :host {
          display: block;
          border: 1px solid #ccc;
          border-radius: 8px;
          padding: 16px;
        }
        
        .header {
          border-bottom: 2px solid #eee;
          padding-bottom: 8px;
          margin-bottom: 8px;
        }
        
        .content {
          color: #333;
        }
      </style>
      <div class="header">
        <slot name="title">Default Title</slot>
      </div>
      <div class="content">
        <slot>Default content</slot>
      </div>
    `;
  }
}

customElements.define("my-card", MyCard);
<!-- Использование с Named Slots -->
<my-card>
  <h2 slot="title">Заголовок карточки</h2>
  <p>Основной контент карточки</p>
  <p>Ещё какой-то контент</p>
</my-card>

Режимы Shadow DOM: open vs closed

// Режим "open" — внешний JavaScript может получить доступ
const shadow = element.attachShadow({ mode: "open" });
const rootElement = element.shadowRoot; // Доступно!

// Режим "closed" — полная инкапсуляция
const shadow = element.attachShadow({ mode: "closed" });
const rootElement = element.shadowRoot; // undefined!

Рекомендация: используйте open для отладки и flexibility, closed для строгой инкапсуляции.

CSS в Shadow DOM

:host селектор

<style>
  /* Стили для самого компонента */
  :host {
    display: block;
    --my-color: blue;
  }
  
  /* Условные стили */
  :host(.active) {
    border: 2px solid green;
  }
  
  :host([disabled]) {
    opacity: 0.5;
  }
</style>
// На уровне Light DOM
const component = document.querySelector("my-component");
component.classList.add("active");
component.setAttribute("disabled", "");

Пробросок CSS переменных

/* Shadow DOM может наследовать CSS переменные */
<style>
  button {
    background-color: var(--button-bg, blue);
    color: var(--button-color, white);
  }
</style>
<!-- Light DOM может задать переменные -->
<style>
  my-button {
    --button-bg: green;
    --button-color: yellow;
  }
</style>
<my-button></my-button>

Практический пример: Модальное окно

class ModalDialog extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: "open" });
    
    shadow.innerHTML = `
      <style>
        :host([open]) .backdrop {
          display: flex;
        }
        
        .backdrop {
          display: none;
          position: fixed;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background-color: rgba(0, 0, 0, 0.5);
          justify-content: center;
          align-items: center;
          z-index: 1000;
        }
        
        .dialog {
          background-color: white;
          border-radius: 8px;
          padding: 24px;
          box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
          max-width: 500px;
          width: 90%;
        }
        
        .close-btn {
          float: right;
          font-size: 24px;
          cursor: pointer;
          color: #999;
        }
      </style>
      <div class="backdrop">
        <div class="dialog">
          <span class="close-btn">&times;</span>
          <slot></slot>
        </div>
      </div>
    `;
  }
  
  connectedCallback() {
    const closeBtn = this.shadowRoot.querySelector(".close-btn");
    closeBtn.addEventListener("click", () => this.close());
    
    const backdrop = this.shadowRoot.querySelector(".backdrop");
    backdrop.addEventListener("click", (e) => {
      if (e.target === backdrop) this.close();
    });
  }
  
  open() {
    this.setAttribute("open", "");
  }
  
  close() {
    this.removeAttribute("open");
  }
}

customElements.define("modal-dialog", ModalDialog);
<modal-dialog id="myModal">
  <h2>Подтверждение</h2>
  <p>Вы уверены?</p>
  <button onclick="document.getElementById(myModal).close()">Отмена</button>
  <button onclick="handleConfirm()">Подтвердить</button>
</modal-dialog>

Преимущества Shadow DOM

  • Инкапсуляция стилей — CSS конфликты исключены
  • Переиспользуемость — компоненты работают везде
  • Безопасность — скрытая структура от внешних скриптов
  • Производительность — браузер может оптимизировать изолированные DOM деревья
  • Семантика — поддержка Web Components стандарта

Когда использовать Shadow DOM

  1. Создание UI библиотек — компоненты работают независимо
  2. Встраиваемые виджеты — не конфликтуют со стилями хоста
  3. Комплексные компоненты — инкапсуляция сложной логики
  4. Третьесторонний код — защита от загрязнения глобального контекста

Когда Shadow DOM НЕ нужен

  • Простые компоненты без стилей
  • Работа в фреймворках типа React (они имеют свою систему)
  • Когда нужен полный контроль браузером (SEO, доступность)

Вывод: Shadow DOM — мощный инструмент для создания действительно переиспользуемых, изолированных компонентов, особенно в контексте Web Components и встраиваемых виджетов.

Для чего нужен теневой DOM в браузере? | PrepBro