צללים DOM בג'אווהסקריפט

צללים DOM בג'אווהסקריפט

מאמר זה מסביר את ה-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 הוא תכונה חזקה של תקן רכיבי ווב שמאפשרת איקפסולציה של סגנונות ומבנה DOM בתוך רכיבים. תכונה זו מונעת התנגשות של סגנונות וסקריפטים בין רכיבים ובין מסמך ה-HTML הראשי.

Shadow DOM בג'אווהסקריפט

Shadow DOM מספק דרך ליצור עץ DOM מוגדר ומקושר לאלמנט DOM רגיל. עץ הצל מבודד מהמסמך הכללי, כך שסגנונות וסקריפטים חיצוניים אינם משפיעים עליו, וסגנונות וסקריפטים פנימיים אינם דולפים החוצה.

לדוגמה, אם תיצור רכיב כפתור מותאם אישית באמצעות Shadow DOM, הסגנונות שלו לא יפריעו לשאר האלמנטים בדף. באופן דומה, אלמנטים עם אותו שם מחלקה לא יתנגשו.

התוכן ה-HTML הרגיל שמחוץ ל-Shadow DOM נקרא Light DOM.

היתרונות של Shadow DOM

  1. הכלה (Encapsulation)

    • ה-Shadow DOM מפריד בין עיצוב ופונקציונליות, ומונע התנגשויות עם סגנונות וסקריפטים גלובליים.
  2. שימוש חוזר (Reusability)

    • רכיבים שנבנים עם Shadow DOM יכולים לשמש בפרויקטים שונים מבלי לחשוש מהתנגשויות בעיצוב.
  3. תחזוקה (Maintainability)

    • ההכלה (Encapsulation) הופכת את הלוגיקה והעיצוב של הרכיב לעצמאיים, ומקלה על ניפוי שגיאות ותחזוקה.

יצירת 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. אלמנט מארח (Host Element)

    • אלמנט DOM רגיל שאליו מוצמד השורש של ה-Shadow (במקרה הזה, #shadow-host).
  2. שורש צל (Shadow Root)

    • שורש עץ הצל שנוצר באמצעות attachShadow.
  3. מצב (Mode)

    • במצב open, קוד ג'אווהסקריפט חיצוני יכול לגשת לשורש הצל דרך element.shadowRoot. מנגד, במצב closed לא מתאפשרת גישה.

עיצוב בתוך Shadow DOM

ל-Shadow DOM יש תחום סגנון משלו. סגנונות שהוגדרו בתוך עץ ה-shadow חלים רק על האלמנטים שבתוך העץ הזה. הנה דוגמה:.

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

גם אם קיימים עיצובים מתנגשים במסמך הראשי, הם לא ישפיעו על הפסקה שבתוך עץ הצל.

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 עולה (bubbles) אל תוך ה־Light DOM, המטרה (target) של האירוע משתנה לאלמנט המארח במקום המקור המקורי.
    • בדוגמה זו, ה־event.target בתוך ה־Shadow DOM הוא אלמנט ה־button עצמו, אך מחוץ ל־Shadow DOM הוא מוחלף לאלמנט ה־div המארח.

Shadow DOM ואלמנטים מותאמים אישית (Custom Elements)

Shadow DOM ואלמנטים מותאמים אישית הם מרכיבים מרכזיים של Web Components. אלו טכנולוגיות המשמשות ליצירת רכיבי ממשק משתמש הניתנים לשימוש חוזר ומבודדים.

 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. אלמנט ה־<p> בתוך my-card אינו מושפע מסגנונות חיצוניים והוא תמיד מוצג בצבע כחול.

חריצים (Slots): פיזור תוכן Light DOM

החריצים (Slots) מאפשרים להקרין (project) תוכן מ-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>
  • אלמנט ה-slot שבפנים ה-Shadow DOM מציג תוכן מ-Light DOM שיש לו את אותו מאפיין slot.

סיום

Shadow DOM הוא כלי חיוני לבניית רכיבים אינטרנטיים חזקים, ניתנים לשימוש חוזר וניתנים לתחזוקה. על ידי קפסולציה של סגנונות ופונקציונליות, הוא מפחית את האפשרות להתנגשויות ומפשט את ניהול בסיס הקוד.

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video