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 & 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 樹與整個文檔相隔離,外部樣式和腳本無法影響它,而其內部樣式和腳本也不會洩漏出去。
例如,使用 Shadow DOM 創建自定義按鈕組件時,其樣式不會干擾頁面上的其他元素。同樣,具有相同 class 名稱的元素也不會產生衝突。
在 Shadow DOM 之外的一般 HTML 內容稱為 Light DOM。
Shadow DOM 的優勢
-
封裝性
Shadow DOM將樣式與功能進行隔離,防止與全域樣式和腳本衝突。
-
可重複使用性
- 以
Shadow DOM建立的元件可以在不同專案中重複使用,無需擔心樣式衝突。
- 以
-
維護性
- 封裝性讓元件的邏輯和樣式獨立存在,使除錯和維護更加容易。
創建 Shadow DOM
要使用 Shadow DOM,需要將一個 shadow 根附加到 HTML 元素上。以下是一個簡單範例:。
1// Select the host element
2const hostElement = document.querySelector('#shadow-host');
3
4// Attach a shadow root
5const shadowRoot = hostElement.attachShadow({ mode: 'open' });解釋
這段程式碼包含以下元素:。
-
主機元素(Host Element)
- 附加 shadow root 的一般 DOM 元素(此例中為
#shadow-host)。
- 附加 shadow root 的一般 DOM 元素(此例中為
-
Shadow Root
- 使用
attachShadow所建立的 shadow tree 根節點。
- 使用
-
模式(Mode)
- 在
open模式下,外部 JavaScript 可以透過element.shadowRoot存取 shadow root。另一方面,closed模式不允許訪問。
- 在
在 Shadow DOM 中的樣式處理
Shadow DOM 有自己獨立的樣式範圍。在 shadow tree 中定義的樣式僅適用於該樹中的元素。以下是一個範例:。
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 tree 中的段落。
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>元素不會受到外部樣式的影響,並且永遠以藍色顯示。
Slots:分配 Light DOM 內容
Slots 可讓你將 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 是構建穩健、可重用且易於維護的網頁組件的重要工具。通過封裝樣式和功能,它減少了衝突的可能性並簡化了代碼庫的管理。
您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。