Shadow DOM trong JavaScript

Shadow DOM trong JavaScript

Bài viết này giải thích về Shadow DOM trong JavaScript.

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>

Hiểu về Shadow DOM

Shadow DOM là một tính năng mạnh mẽ của chuẩn Web Components, cho phép đóng gói các kiểu dáng (styles) và cấu trúc DOM trong các thành phần. Tính năng này ngăn chặn sự xung đột giữa các kiểu dáng và script của các thành phần và tài liệu chính.

Shadow DOM trong JavaScript

Shadow DOM cung cấp một cách tạo cây DOM phân vùng liên kết với một phần tử DOM thông thường. Cây Shadow này được cách ly khỏi tài liệu tổng thể, nơi các kiểu dáng và script bên ngoài không tác động đến nó, cũng như các kiểu dáng và script bên trong không bị rò rỉ ra ngoài.

Ví dụ, nếu bạn tạo một thành phần nút bấm tùy chỉnh sử dụng Shadow DOM, các kiểu dáng của nó sẽ không ảnh hưởng đến các phần tử khác trên trang web. Tương tự, các phần tử có cùng tên class sẽ không bị xung đột.

Nội dung HTML thông thường bên ngoài Shadow DOM được gọi là Light DOM.

Lợi ích của Shadow DOM

  1. Đóng gói (Encapsulation)

    • Shadow DOM tách biệt kiểu dáng và chức năng, ngăn ngừa xung đột với các style và script toàn cục.
  2. Tái sử dụng (Reusability)

    • Các thành phần được xây dựng với Shadow DOM có thể được tái sử dụng ở nhiều dự án khác nhau mà không lo về xung đột kiểu dáng.
  3. Dễ bảo trì (Maintainability)

    • Đóng gói giúp logic và kiểu dáng của thành phần được tách biệt, giúp dễ dàng gỡ lỗi và bảo trì hơn.

Tạo Shadow DOM

Để sử dụng Shadow DOM, bạn cần gắn một shadow root vào một phần tử HTML. Dưới đây là một ví dụ đơn giản:.

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

Giải thích

Đoạn mã này bao gồm các thành phần sau:.

  1. Thành phần chủ (Host Element)

    • Một phần tử DOM thông thường mà shadow root được gắn vào (trong trường hợp này là #shadow-host).
  2. Shadow Root

    • Gốc của cây shadow được tạo ra bằng cách sử dụng attachShadow.
  3. Chế độ (Mode)

    • Ở chế độ open, JavaScript bên ngoài có thể truy cập vào shadow root thông qua element.shadowRoot. Ngược lại, chế độ closed không cho phép truy cập.

Tạo kiểu trong Shadow DOM

Shadow DOM có phạm vi kiểu dáng riêng của nó. Các kiểu dáng được định nghĩa bên trong cây bóng chỉ áp dụng cho các phần tử bên trong cây đó. Dưới đây là một ví dụ:.

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`;

Ngay cả khi có các kiểu dáng xung đột trong tài liệu chính, chúng cũng không ảnh hưởng đến đoạn văn bên trong cây 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`;
  • Đoạn văn bên trong Shadow DOM vẫn giữ màu xanh lá, trong khi đoạn bên ngoài có màu đỏ.

Các sự kiện bên trong Shadow DOM

Các sự kiện bên trong Shadow DOM tương tự như các sự kiện DOM thông thường nhưng có thể hoạt động khác biệt về mặt lan truyền do tính đóng gói.

Dưới đây là một ví dụ:.

 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});
  • Khi nút được nhấn, cả hai trình nghe đều được kích hoạt, minh họa hành vi của sự kiện bong bóng.
  • Khi một sự kiện phát sinh bên trong Shadow DOM lan lên Light DOM, thuộc tính target của sự kiện sẽ được thay đổi thành phần tử chủ thay vì nguồn gốc ban đầu.
    • Trong ví dụ này, event.target bên trong Shadow DOM là phần tử button thực tế, nhưng bên ngoài Shadow DOM, nó được thay thế bằng phần tử chủ div.

Shadow DOM và các phần tử tùy chỉnh

Shadow DOM và các phần tử tùy chỉnh là thành phần chính của Web Components. Chúng là các công nghệ được sử dụng để tạo ra các thành phần giao diện người dùng có thể tái sử dụng và đóng gói.

 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);

Tài liệu chính chứa HTML như sau:.

1<my-card></my-card>
  • Bằng cách sử dụng Shadow DOM bên trong các phần tử tùy chỉnh, bạn có thể xây dựng các thành phần có thể tái sử dụng và chống xung đột kiểu dáng. Trong đoạn mã này, một thẻ có tên my-card được tạo ra và liên kết với lớp MyCard. Phần tử <p> bên trong my-card không bị ảnh hưởng bởi các kiểu dáng bên ngoài và luôn được hiển thị màu xanh dương.

Slots: Phân phối nội dung Light DOM

Slots cho phép bạn chèn nội dung Light DOM vào bên trong Shadow DOM. Dưới đây là một ví dụ:.

 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);

Tài liệu chính chứa HTML như sau:.

1<my-element>
2    <h3 slot="header">Header Content</h1>
3    <p slot="content">Main Content</p>
4</my-element>
  • Phần tử slot bên trong Shadow DOM sẽ hiển thị nội dung Light DOM với thuộc tính slot tương ứng.

Kết luận

Shadow DOM là một công cụ quan trọng để xây dựng các thành phần web mạnh mẽ, có thể tái sử dụng và dễ duy trì. Bằng cách đóng gói kiểu dáng và chức năng, nó giảm khả năng xung đột và đơn giản hóa việc quản lý mã nguồn.

Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.

YouTube Video