JavaScript 中的 Shadow DOM

JavaScript 中的 Shadow DOM

本文将解释 JavaScript 中的 Shadow DOM。

YouTube Video

javascript-html-shadow-dom.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    h1, h2 {
 32        font-size: 1.2rem;
 33        color: #007bff;
 34        margin-top: 0.5em;
 35        margin-bottom: 0.5em;
 36        border-left: 5px solid #007bff;
 37        padding-left: 0.6em;
 38        background-color: #e9f2ff;
 39    }
 40
 41    button {
 42        display: block;
 43        margin: 1em auto;
 44        padding: 0.75em 1.5em;
 45        font-size: 1rem;
 46        background-color: #007bff;
 47        color: white;
 48        border: none;
 49        border-radius: 6px;
 50        cursor: pointer;
 51        transition: background-color 0.3s ease;
 52    }
 53
 54    button:hover {
 55        background-color: #0056b3;
 56    }
 57
 58    #output {
 59        margin-top: 1em;
 60        background-color: #1e1e1e;
 61        color: #0f0;
 62        padding: 1em;
 63        border-radius: 8px;
 64        min-height: 200px;
 65        font-family: Consolas, monospace;
 66        font-size: 0.95rem;
 67        overflow-y: auto;
 68        white-space: pre-wrap;
 69    }
 70
 71    .highlight {
 72        outline: 3px solid #ffc107; /* yellow border */
 73        background-color: #fff8e1;  /* soft yellow background */
 74        transition: background-color 0.3s ease, outline 0.3s ease;
 75    }
 76
 77    .active {
 78        background-color: #28a745; /* green background */
 79        color: #fff;
 80        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 81        transition: background-color 0.3s ease, box-shadow 0.3s ease;
 82    }
 83  </style>
 84</head>
 85<body>
 86    <div class="container">
 87        <h2>HTML Sample</h2>
 88        <div id="content"></div>
 89        <div id="shadow-host">Shadow Root Element</div>
 90        <my-card></my-card>
 91    </div>
 92
 93    <div class="container">
 94        <h1>JavaScript Console</h1>
 95        <button id="executeBtn">Execute</button>
 96        <div id="output"></div>
 97    </div>
 98
 99    <div>
100        <h2>Slot Sample</h2>
101        <my-element>
102            <h3 slot="header">Header Content</h1>
103            <p slot="content">Main Content</p>
104        </my-element>
105    </div>
106
107    <script>
108        // Override console.log to display messages in the #output element
109        (function () {
110            const originalLog = console.log;
111            console.log = function (...args) {
112                originalLog.apply(console, args);
113                const output = document.getElementById('output');
114                output.textContent += args.map(String).join(' ') + '\n';
115            };
116        })();
117
118        document.getElementById('executeBtn').addEventListener('click', () => {
119            // Prevent multiple loads
120            if (document.getElementById('externalScript')) return;
121
122            const script = document.createElement('script');
123            script.src = 'javascript-html-shadow-dom.js';
124            script.id = 'externalScript';
125            //script.onload = () => console.log('javascript-html-shadow-dom.js loaded and executed.');
126            //script.onerror = () => console.log('Failed to load javascript-html-shadow-dom.js.');
127            document.body.appendChild(script);
128        });
129    </script>
130</body>
131</html>

理解 Shadow DOM

Shadow DOM 是 Web 组件标准中的一个强大特性,它实现了组件内样式和 DOM 结构的封装。该特性可以防止组件与主文档之间的样式和脚本冲突。

JavaScript 中的 Shadow DOM

Shadow DOM 提供了一种与常规 DOM 元素关联的作用域 DOM 树的方法。这个影子树是与整体文档隔离的,外部样式和脚本无法影响它,其内部样式和脚本也不会外泄。

例如,如果您使用 Shadow DOM 创建一个自定义按钮组件,它的样式将不会影响页面上的其他元素。同样,具有相同类名的元素也不会产生冲突。

Shadow DOM 外部的普通 HTML 内容称为 Light DOM

Shadow DOM 的优点

  1. 封装

    • Shadow DOM 实现了样式和功能的分离,避免了与全局样式和脚本的冲突。
  2. 可重用性

    • 使用 Shadow DOM 构建的组件可以在不同项目中复用,无需担心样式冲突。
  3. 可维护性

    • 封装使组件的逻辑和样式独立,便于调试和维护。

创建 Shadow DOM

要使用 Shadow DOM,您需要将一个影子根附加到 HTML 元素。下面是一个简单的示例:。

1// Select the host element
2const hostElement = document.querySelector('#shadow-host');
3
4// Attach a shadow root
5const shadowRoot = hostElement.attachShadow({ mode: 'open' });

解释

该代码包括以下元素:。

  1. 宿主元素

    • 一个附加了 shadow root 的普通 DOM 元素(本例为 #shadow-host)。
  2. Shadow 根节点

    • 通过 attachShadow 创建的 shadow 树的根节点。
  3. 模式

    • open 模式下,外部 JavaScript 可以通过 element.shadowRoot 访问 shadow root。另一方面,closed 模式是不可访问的。

Shadow DOM 内部样式

“Shadow DOM”具有其独立的样式作用域。在影子树中定义的样式仅适用于该树内的元素。以下是一个示例:。

1// Add content to the shadow root
2shadowRoot.innerHTML = `
3    <style>
4        p {
5            color: green;
6        }
7    </style>
8    <p>Scoped style inside Shadow DOM.</p>
9`;

即使主文档中存在冲突的样式,也不会影响 shadow 树内的段落。

1const content = document.getElementById('content');
2content.innerHTML = `
3    <style>
4        p {
5            color: red;
6        }
7    </style>
8    <p>This is in the main DOM.</p>
9`;
  • Shadow DOM 内的段落保持绿色,而外部的为红色。

“Shadow DOM”中的事件

“Shadow DOM”中的事件与普通 DOM 事件类似,但由于封装的原因,其传播行为可能有所不同。

以下是一个示例:。

 1// Add an event listener inside Shadow DOM
 2shadowRoot.innerHTML = `
 3    <div id="button-container">
 4        <button id="shadow-button">Click Me</button>
 5    </div>
 6`;
 7
 8shadowRoot.querySelector('#shadow-button').addEventListener('click', (event) => {
 9    console.log('Button : Button clicked inside Shadow DOM');
10    console.log(event.target);
11});
12
13shadowRoot.querySelector('#button-container').addEventListener('click', (event) => {
14    console.log('Container : Button clicked inside Shadow DOM');
15    console.log(event.target);
16});
17
18hostElement.addEventListener('click', (event) => {
19    console.log('Event bubbled to the host element');
20    console.log(event.target);
21});
22
23document.addEventListener('click', (event) => {
24    console.log('Document listener');
25    console.log(event.target);
26});
  • 当按钮被点击时,两个监听器都会被触发,这演示了事件冒泡的行为。
  • 当从 Shadow DOM 内部触发的事件冒泡到 Light DOM 时,事件的 target 会被重写为宿主元素,而不是原始源。
    • 在本例中,在 Shadow DOM 内部的 event.target 是实际的 button 元素,但在 Shadow DOM 外部,它会被替换为宿主 div 元素。

Shadow DOM 与自定义元素

Shadow DOM 和自定义元素是 Web Components 的关键组件。它们是用于创建可复用且封装的 UI 组件的技术。

 1class MyCard extends HTMLElement {
 2    constructor() {
 3        super();
 4        const shadow = this.attachShadow({ mode: 'open' });
 5        shadow.innerHTML = `
 6        <style>
 7            p {
 8                color: blue;
 9            }
10        </style>
11        <p>I'm inside shadow DOM</p>
12        `;
13    }
14}
15
16customElements.define('my-card', MyCard);

主文档包含如下 HTML:。

1<my-card></my-card>
  • 通过在自定义元素中使用 Shadow DOM,你可以构建可复用且不容易发生样式冲突的组件。在此代码中,创建了一个名为 my-card 的标签,并将其关联到 MyCard 类。my-card 内部的 <p> 元素不会受到外部样式的影响,并且始终以蓝色显示。

插槽:分发 Light DOM 内容

插槽允许你将 Light DOM 内容投影到 Shadow DOM 中。以下是一个示例:。

 1class MyElement extends HTMLElement {
 2    constructor() {
 3        super();
 4        const shadow = this.attachShadow({ mode: 'open' });
 5
 6        shadow.innerHTML = `
 7        <style>
 8            .container {
 9                border: 2px solid #ccc;
10                padding: 16px;
11                border-radius: 8px;
12                font-family: sans-serif;
13            }
14            .header {
15                font-size: 1.2em;
16                color: darkblue;
17                margin-bottom: 8px;
18            }
19            .content {
20                font-size: 1em;
21                color: #333;
22            }
23        </style>
24        <div class="container">
25            <div class="header">
26                <slot name="header"></slot>
27            </div>
28            <div class="content">
29                <slot name="content"></slot>
30            </div>
31        </div>
32        `;
33    }
34}
35
36customElements.define('my-element', MyElement);

主文档包含如下 HTML:。

1<my-element>
2    <h3 slot="header">Header Content</h1>
3    <p slot="content">Main Content</p>
4</my-element>
  • Shadow DOM 内的 slot 元素会显示具有对应 slot 属性的 Light DOM 内容。

结论

“Shadow DOM”是构建健壮的、可重用的和可维护的 Web 组件的重要工具。通过封装样式和功能,可以减少冲突的可能性,并简化代码库的管理。

您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。

YouTube Video