Shadow DOM проти звичайного DOM
Звичайний (light) DOM, це основне дерево документа, а Shadow DOM, це вкладене ізольоване DOM-дерево всередині елемента, яке має свої стилі, розмітку й поведінку і не впливає на зовнішній документ. Простими словами: light DOM, це «поверхня сайту», а Shadow DOM, це «нутрощі компонента», сховані від решти світу.
Теорія
TL;DR
- Light DOM, це все, що ви пишете в HTML і бачите в DevTools; Shadow DOM живе всередині host-елемента.
- Light DOM підпорядкований глобальним CSS, Shadow DOM від них ізольований.
- До light DOM ходять через
document.querySelector(), до тіньового, тільки черезshadowRoot.querySelector(). - Слоти (
<slot>) є лише в Shadow DOM: через них вміст із light DOM показується всередині компонента. - Shadow DOM потрібен для інкапсуляції компонентів, ізоляції стилів і самодостатніх елементів на кшталт
<video>.
Швидкий приклад
const card = document.querySelector('.card');
const shadow = card.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
button { color: blue; }
</style>
<button>Внутрішня</button>
`;Тепер у card є внутрішній Shadow DOM, який ізольований від зовнішніх стилів, не видно у звичайному дереві документа (а при mode: 'closed' до нього взагалі не дістатися ззовні) і який керує власними елементами. Глобальне правило button { color: red; } не перефарбує цю кнопку, бо вона живе у своєму тіньовому дереві.
Означення: light DOM і Shadow DOM
| Тип | Що це таке |
|---|---|
| Звичайний (light) DOM | Основне дерево HTML-документа: усе, що ви пишете в HTML і бачите в DevTools. |
| Shadow DOM | Вкладене ізольоване DOM-дерево всередині елемента (компонента), яке має свої стилі, розмітку й поведінку і не впливає на зовнішній документ. |
Для порівняння, звичайна картка в light DOM:
<div class="card">
<h2>Заголовок</h2>
<button>Клік</button>
</div>Тут усе видно в основному DOM, кнопку можна дістати через document.querySelector('button'), а глобальні стилі впливають на все: правило button { color: red; } зробить кнопку червоною.
Ключові відмінності
| Характеристика | Звичайний DOM (light) | Shadow DOM |
|---|---|---|
| Де живе | У дереві документа | Усередині host-елемента |
| Видимість у DevTools | Так | Так, але приховано за замовчуванням, вмикається опцією «Show user agent shadow DOM» |
| Стилізація | Підпорядкований глобальним CSS | Ізольований від глобальних стилів |
| Ізоляція | Немає, стилі та JS впливають одне на одного | Повна, ізольований контекст |
| Де використовують | Будь-який HTML | Web Components, вбудовані елементи (<video>, <input> та інші) |
| Взаємодія | Прямо через document.querySelector() | Тільки через shadowRoot.querySelector() |
| Слоти | Немає | Є (<slot> для вставки вмісту з light DOM) |
Структура і слоти
<body>
<my-card> <!-- host-елемент -->
#shadow-root (shadow DOM)
<style>...</style>
<button>Click</button>
#light-dom (звичайний DOM)
<p slot="content">...</p>
</my-card>
</body>Тут my-card, це елемент-хост, #shadow-root, це його внутрішній Shadow DOM, а зовнішні елементи можуть передавати вміст усередину через <slot>. Важливо: слотований <p> фізично залишається в light DOM і підпадає під глобальні стилі, просто показується у визначеному місці тіньового дерева.
Доступ із коду
Із light DOM видно лише «оболонку» компонента:
console.log(document.querySelector('my-card').innerHTML);
// Порожньо, бо нутрощі заховані в Shadow DOMДо тіньового дерева ходять лише за посиланням на корінь:
const shadow = document.querySelector('my-card').shadowRoot;
console.log(shadow.querySelector('button')); // <button>Click</button>Якщо дерево створене з mode: 'closed', shadowRoot буде null, і ззовні до внутрішніх вузлів не дістатися взагалі.
Де це потрібно на практиці
Shadow DOM беруть, коли треба:
- інкапсулювати компонент (веб-компоненти, UI-бібліотеки);
- ізолювати стилі, щоб глобальні CSS не ламали зовнішній вигляд;
- зробити перевикористовуваний елемент, наприклад
<custom-slider>; - зробити компонент самодостатнім, як
<video>чи<select>.
Життєвий приклад. У звичайному DOM ви перевизначили стилі кнопок на всьому сайті, і раптом кнопки всередині компонента «поламалися», бо стилі глобальні. З Shadow DOM компонент зі своїми стилями ізольований, і жодне глобальне button { color: red } на нього не вплине.
| Критерій | Звичайний DOM | Shadow DOM |
|---|---|---|
| Видимість | Доступний усьому коду | Прихований і ізольований |
| Стилі | Спільні | Ізольовані |
| Використання | Повсякденний HTML | Компоненти і вбудовані елементи |
| Взаємодія | Через document | Через shadowRoot |
| Безпека | Ізоляції немає | Є інкапсуляція |
Типові помилки
- Шукати внутрішні вузли компонента через
document.querySelector(): селектор не перетинає межу тіньового дерева. - Думати, що глобальний CSS «просто спрацює» всередині компонента: туди стилі передають через власний
<style>, CSS-змінні або::part. - Плутати слот із перенесенням вузла: слотований вміст лишається в light DOM, а не копіюється в тіньове дерево.
- Вважати
closedзахистом даних: це лише ускладнення доступу, реальної безпеки воно не дає. - Забувати про події: щоб подія з тіньового дерева піднялася назовні, її створюють з
composed: true, і ззовні їїtargetбуде хостом.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.