מבוא
Web Components היא קבוצה של תקנים מתקדמים שמאפשרים:
- אפשרות לפתח ווידג'טים
- ...ואפשר לעשות בו שימוש חוזר בצורה אמינה
- …והם לא יגרמו לשיבושים בדפים אם הפרטים הפנימיים של ההטמעה ישתנו בגרסה הבאה של הרכיב.
האם זה אומר שצריך להחליט מתי להשתמש ב-HTML/JavaScript ומתי להשתמש ברכיבי Web? לא. אפשר להשתמש ב-HTML וב-JavaScript כדי ליצור תוכן חזותי אינטראקטיבי. ווידג'טים הם רכיבים חזותיים אינטראקטיביים. מומלץ להשתמש ביכולות שלכם ב-HTML וב-JavaScript כשאתם מפתחים ווידג'ט. סטנדרטים של רכיבי אינטרנט נועדו לעזור לכם לעשות זאת.
אבל יש בעיה בסיסית שהופכת את השימוש בווידג'טים ל-HTML ומ-JavaScript כקשה לשימוש: עץ ה-DOM שבתוך ווידג'ט לא נכלל בשאר חלקי הדף. אם אין מכסה כזו, ייתכן שגיליון העיצוב של המסמך יחול בטעות על חלקים בתוך הווידג'ט. ה-JavaScript עלול לשנות בטעות חלקים בתוך הווידג'ט. המזהים עשויים לחפוף למזהים בתוך הווידג'ט, וכן הלאה.
Web Components מורכב משלושה חלקים:
Shadow DOM פותר את הבעיה של אנקפסולציה של עץ DOM. ארבעת החלקים של רכיבי האינטרנט מיועדים לפעול יחד, אבל אפשר גם לבחור את החלקים של רכיבי האינטרנט שבהם רוצים להשתמש. המדריך הזה מסביר איך להשתמש ב-shadow DOM.
שלום, עולם הצללים
בעזרת Shadow DOM, אפשר לשייך לרכיבים צומת חדש. הסוג החדש של צומת נקרא שורש צל. רכיב עם שורש צל המשויך אליו נקרא מארח צל. התוכן של מארח צל לא עובר עיבוד, ובמקום זאת עובר עיבוד התוכן של שורש הצל.
לדוגמה, אם יש לכם קוד מסמנים כזה:
<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>
אז במקום
<button id="ex1a">Hello, world!</button>
<script>
function remove(selector) {
Array.prototype.forEach.call(
document.querySelectorAll(selector),
function (node) { node.parentNode.removeChild(node); });
}
if (!HTMLElement.prototype.createShadowRoot) {
remove('#ex1a');
document.write('<img src="SS1.png" alt="Screenshot of a button with \'Hello, world!\' on it.">');
}
</script>
הדף שלך נראה כך
<button id="ex1b">Hello, world!</button>
<script>
(function () {
if (!HTMLElement.prototype.createShadowRoot) {
remove('#ex1b');
document.write('<img src="SS2.png" alt="Screenshot of a button with \'Hello, shadow world!\' in Japanese on it.">');
return;
}
var host = document.querySelector('#ex1b');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
})();
</script>
בנוסף, אם JavaScript בדף שואל מה הערך של textContent
של הלחצן, הוא לא יקבל את הערך 'こんにちは、影の世界!' אלא את הערך 'Hello, world!', כי עץ המשנה של DOM מתחת לשורש הצל הוא בתוך קפסולה.
הפרדה בין תוכן לבין הצגה
עכשיו נבחן את האפשרות להשתמש ב-shadow DOM כדי להפריד תוכן מהמצגת. נניח שיש לנו את תג השם הזה:
<style>
.ex2a.outer {
border: 2px solid brown;
border-radius: 1em;
background: red;
font-size: 20pt;
width: 12em;
height: 7em;
text-align: center;
}
.ex2a .boilerplate {
color: white;
font-family: sans-serif;
padding: 0.5em;
}
.ex2a .name {
color: black;
background: white;
font-family: "Marker Felt", cursive;
font-size: 45pt;
padding-top: 0.2em;
}
</style>
<div class="ex2a outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
Bob
</div>
</div>
זהו ה-Markup. זה מה שצריך לכתוב היום. היא לא משתמשת ב-Shadow DOM:
<style>
.outer {
border: 2px solid brown;
border-radius: 1em;
background: red;
font-size: 20pt;
width: 12em;
height: 7em;
text-align: center;
}
.boilerplate {
color: white;
font-family: sans-serif;
padding: 0.5em;
}
.name {
color: black;
background: white;
font-family: "Marker Felt", cursive;
font-size: 45pt;
padding-top: 0.2em;
}
</style>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
Bob
</div>
</div>
מכיוון שעץ ה-DOM חסר אנקפסולציה, כל המבנה של תג השם חשוף למסמך. אם אלמנטים אחרים בדף ישתמשו בטעות באותם שמות של כיתות לצורך עיצוב או סקריפטים, נהיה בצרות.
כך נוכל למנוע מצב של חוסר שביעות רצון.
שלב 1: הסתרת פרטי המצגת
מבחינה סמנטית, סביר להניח שרק זה חשוב לנו:
- זהו תג שם.
- השם הוא 'Bob'.
קודם כול, אנחנו כותבים סימון שקרובה יותר לסמנטיקה האמיתית שאנחנו רוצים:
<div id="nameTag">Bob</div>
לאחר מכן, אנחנו מכניסים את כל הסגנונות וה-divs ששימשו להצגה לרכיב <template>
:
<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<span class="unchanged"><style>
.outer {
border: 2px solid brown;
… same as above …
</style>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
Bob
</div>
</div></span>
</template>
בשלב הזה, 'Bob' הוא הדבר היחיד שעבר רינדור. מכיוון שהעברנו את רכיבי ה-DOM לתצוגה בתוך רכיב <template>
, הם לא עוברים רינדור, אבל אפשר לגשת אליהם מ-JavaScript. עכשיו אנחנו עושים זאת כדי לאכלס את שורש הצללית:
<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
עכשיו, אחרי שהגדרתם root בצל, תג השם ימומש שוב. אם תלחצו לחיצה ימנית על תג השם ותבדקו את המרכיב, תראו שהוא תג עיצוב סמנטי נהדר:
<div id="nameTag">Bob</div>
זה מראה שבעזרת Shadow DOM, הסתירו מהמסמך את פרטי התצוגה של תג השם. פרטי ההצגה נכללים ב-Shadow DOM.
שלב 2: הפרדת התוכן מהמצגת
תג השם שלנו מסתיר עכשיו את פרטי התצוגה מהדף, אבל הוא לא מפריד בפועל בין התצוגה לתוכן, כי אמנם התוכן (השם 'Bob') נמצא בדף, אבל השם שמוצג הוא השם שהעתקנו לשורש הצל. אם נרצה לשנות את השם בתג השם, נצטרך לעשות זאת בשני מקומות, ויכול להיות שהם לא יסונכרנו.
רכיבי HTML הם רכיבים מורכבים – לדוגמה, אפשר להוסיף לחצן לטבלה. כאן אנחנו צריכים קומפוזיציה: תג השם צריך להיות קומפוזיציה של הרקע האדום, הטקסט 'היי!' והתוכן שמופיע בתג השם.
אתם, ככותבי הרכיבים, מגדירים איך הקומפוזיציה עובדת עם הווידג'ט באמצעות רכיב חדש שנקרא <content>
. כך נוצרת נקודת הטמעה בהצגת הווידג'ט, ונקודת ההטמעה בוחרת תוכן ממארח הצל כדי להציג בנקודה הזו.
אם נשנה את תגי העיצוב ב-SHAdow DOM כך:
<span class="unchanged"><template id="nameTagTemplate">
<style>
…
</style></span>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
<content></content>
</div>
</div>
<span class="unchanged"></template></span>
כשמתבצע עיבוד של תג השם, התוכן של מארח הצל מוקרן למיקום שבו מופיע האלמנט <content>
.
עכשיו המבנה של המסמך פשוט יותר כי השם מופיע רק במקום אחד – במסמך. אם תצטרכו לעדכן את שם המשתמש בדף, תוכלו פשוט לכתוב:
document.querySelector('#nameTag').textContent = 'Shellie';
וזה הכול. הדפדפן מעדכן באופן אוטומטי את הרינדור של תג השם, כי אנחנו מייצגים את התוכן של תג השם באמצעות <content>
.
<div id="ex2b">
עכשיו יש לנו הפרדה בין התוכן לבין הצגתו. התוכן נמצא במסמך, והתצוגה נמצאת ב-Shadow DOM. הם מסונכרנים אוטומטית על ידי הדפדפן כשמגיע הזמן לעבד משהו.
שלב 3: רווח
על ידי הפרדה בין התוכן לבין המצגת, נוכל לפשט את הקוד שמבצע שינויים בתוכן – בדוגמה של תג השם, הקוד צריך לטפל רק במבנה פשוט שמכיל <div>
אחד במקום כמה פעמים.
עכשיו אם נשנה את המצגת, אנחנו לא צריכים לשנות אף אחד מהקוד!
לדוגמה, נניח שאנחנו רוצים לבצע לוקליזציה של תג השם שלנו. הוא עדיין תג שם, ולכן התוכן הסמנטי במסמך לא משתנה:
<div id="nameTag">Bob</div>
קוד ההגדרה של הרמה הבסיסית (root) של הצללית לא משתנה. מה שמתווסף לשורש הצללים משתנה:
<template id="nameTagTemplate">
<style>
.outer {
border: 2px solid pink;
border-radius: 1em;
background: url(sakura.jpg);
font-size: 20pt;
width: 12em;
height: 7em;
text-align: center;
font-family: sans-serif;
font-weight: bold;
}
.name {
font-size: 45pt;
font-weight: normal;
margin-top: 0.8em;
padding-top: 0.2em;
}
</style>
<div class="outer">
<div class="name">
<content></content>
</div>
と申します。
</div>
</template>
זהו שיפור משמעותי לעומת המצב באינטרנט כיום, כי קוד עדכון השם יכול להיות תלוי במבנה של הרכיב שהוא פשוט ועקבי. קוד עדכון השם לא צריך לדעת את המבנה שמשמש לעיבוד. כשאנחנו בודקים מה מוצג בתוכן, השם מופיע בשנייה באנגלית (אחרי הכיתוב "שלום! קוראים לי), אבל קודם ביפנית (לפני "と申します"). ההבדל הזה לא רלוונטי מבחינה סמנטית מבחינת עדכון השם שמוצג, ולכן קוד עדכון השם לא צריך לדעת על הפרט הזה.
בונוס: תחזית מתקדמת
בדוגמה שלמעלה, האלמנט <content>
בוחר בקפידה את כל התוכן מהמארח בצל. באמצעות המאפיין select
אפשר לקבוע מה רכיב התוכן יציג. אפשר גם להשתמש בכמה רכיבי תוכן.
לדוגמה, אם יש לכם מסמך שמכיל את הטקסט הזה:
<div id="nameTag">
<div class="first">Bob</div>
<div>B. Love</div>
<div class="email">bob@</div>
</div>
ו-shadow root שמשתמש בסלקטורים של CSS כדי לבחור תוכן ספציפי:
<div style="background: purple; padding: 1em;">
<div style="color: red;">
<content **select=".first"**></content>
</div>
<div style="color: yellow;">
<content **select="div"**></content>
</div>
<div style="color: blue;">
<content **select=".email">**</content>
</div>
</div>
הרכיב <div class="email">
תואם גם לרכיב <content select="div">
וגם לרכיב <content
select=".email">
. כמה פעמים מופיעה כתובת האימייל של בועז ובאילו צבעים?
התשובה היא שכתובת האימייל של בועז מופיעה פעם אחת והיא צהובה.
הסיבה לכך היא שכמו שאנשים שמבצעים פריצות ב-Shadow DOM יודעים, בניית העץ של מה שעבר עיבוד בפועל במסך היא כמו מסיבה גדולה. רכיב התוכן הוא ההזמנה שמאפשרת לתוכן מהמסמך להיכנס לצד העורפי של הצד שמייצג את ה-Shadow DOM. ההזמנות האלה נשלחות לפי הסדר. מי מקבל הזמנה תלוי למי היא מיועדת (כלומר, המאפיין select
). התוכן, לאחר ההזמנה, תמיד מאשר את ההזמנה (מי לא היה רוצה?!) ונעלם. אם הזמנה נוספת תישלח לכתובת הזו שוב,
אין אף אחד בבית והם לא יגיעו למסיבה.
בדוגמה שלמעלה, הערך <div class="email">
תואם גם לבורר div
וגם לבורר .email
, אבל מכיוון שרכיב התוכן עם הבורר div
מופיע מוקדם יותר במסמך, הערך <div class="email">
עובר למסיבה הצהובה ואף אחד לא זמין למסיבה הכחולה. (יכול להיות שלמה הוא כל כך כחול, למרות שהאומללות אוהבת חברות, אז
אי אפשר לדעת.)
אם מזמינים משהו למסיבות לא, הוא לא יעבור עיבוד בכלל. זה מה שקרה לטקסט Hello, world בדוגמה הראשונה. האפשרות הזו שימושית כשרוצים ליצור עיבוד שונה באופן קיצוני: כותבים את המודל הסמנטי במסמך, שהוא הגלוי לסקריפטים בדף, אבל מסתירים אותו למטרות עיבוד ומקשרים אותו למודל עיבוד שונה מאוד ב-Shadow DOM באמצעות JavaScript.
לדוגמה, ב-HTML יש חלונית נוחה לבחירת תאריך. אם כותבים <input
type="date">
, נוצר יומן קופץ. אבל מה קורה אם רוצים לאפשר למשתמש לבחור טווח תאריכים לחופשה באי כקינוח (אתם יודעים… עם ערסלים שנוצרו מענבים אדומים). כך מגדירים את המסמך:
<div class="dateRangePicker">
<label for="start">Start:</label>
<input type="date" name="startDate" id="start">
<br>
<label for="end">End:</label>
<input type="date" name="endDate" id="end">
</div>
אלא ליצור DOM בצל שמשתמש בטבלה כדי ליצור לוח שנה מעוצב שמבליט את טווח התאריכים וכו'. כשהמשתמש לוחץ על הימים ביומן, הרכיב מעדכן את המצב בערכים של startDate ו-endDate. כשהמשתמש שולח את הטופס, הערכים מרכיבי הקלט האלה נשלחים.
למה כדאי לכלול תוויות במסמך אם הן לא יוצגו? הסיבה לכך היא שאם משתמש צופה בטופס בדפדפן שלא תומך ב-Shadow DOM, עדיין אפשר להשתמש בטופס, אבל הוא לא נראה יפה. המשתמש רואה משהו כזה:
<div class="dateRangePicker">
<label for="start">Start:</label>
<input type="date" name="startDate" id="start">
<br>
<label for="end">End:</label>
<input type="date" name="endDate" id="end">
</div>
עברתם את קורס Shadow DOM למתחילים
אלה העקרונות הבסיסיים של Shadow DOM – סיימתם את קורס Shadow DOM 101! אפשר לעשות הרבה יותר עם Shadow DOM. לדוגמה, אפשר להשתמש בכמה צללים במארח צללים אחד, או בצללים בתצוגת עץ לצורך אנקפסולציה, או לתכנן את הדף באמצעות תצוגות מבוססות-מודל (MDV) ו-Shadow DOM. ורכיבי אינטרנט הם יותר מ-Sadow DOM.
נסביר עליהם בהמשך.