צל DOM 101

Dominic Cooney
Dominic Cooney

מבוא

Web Elements (רכיבי אינטרנט) היא קבוצה של תקנים חדשניים אשר:

  1. אפשר לבנות ווידג'טים
  2. ...ואפשר לעשות בו שימוש חוזר בצורה אמינה
  3. ...ושלא יגרמו לשיבושים בדפים אם הגרסה הבאה של הרכיב משנה את פרטי ההטמעה הפנימית.

כלומר צריך להחליט מתי להשתמש ב-HTML/JavaScript, וכן מתי להשתמש ברכיבי אינטרנט? לא. HTML ו-JavaScript יכולים ליצור ורכיבים חזותיים אינטראקטיביים. ווידג'טים הם דברים חזותיים אינטראקטיביים. הוא הגיוני למנף את מיומנויות HTML ו-JavaScript על פיתוח ווידג'ט. הסטנדרטים של רכיבי האינטרנט נועדו לעזור תעשו את זה.

אבל יש בעיה בסיסית שגורמת לווידג'טים קשה להשתמש ב-HTML וב-JavaScript: עץ ה-DOM שבתוך ווידג'ט אינו מופיע בהמשך הדף. היעדר המעטפת פירושו שגיליון הסגנונות של המסמך עלול לחול בטעות על חלקים בתוך הווידג'ט. JavaScript עלול לשנות בטעות חלקים בתוך הווידג'ט. יכול להיות שהמזהים יחפפו למזהים בתוך הווידג'ט. וכן הלאה.

רכיבי האינטרנט מורכבים משלושה חלקים:

  1. תבניות
  2. DOM של Shadow DOM
  3. רכיבים מותאמים אישית

Shadow DOM מטפל בבעיית הדחיסה של עץ DOM. ארבעה חלקים של רכיבי אינטרנט מיועדים לפעול יחד, הוא יכול גם לבחור באילו חלקים של רכיבי אינטרנט להשתמש. הזה המדריך מראה איך להשתמש ב-Sshadow 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, לא ניתן לקבל אותו “”, ❌ ち山世界!, אבל “שלום, עולם!" כי עץ המשנה 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>

הנה תג העיצוב. זה מה שתכתוב היום. אבל היא לא צריך להשתמש ב-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>

לאחר מכן אנחנו מכניסים את כל הסגנונות וה-div שמשמשים למצגת רכיב <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);

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

<div id="nameTag">Bob</div>

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

שלב 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">

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

שלב 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>

זהו שיפור משמעותי לעומת המצב הקיים באינטרנט, מאחר קוד עדכון השם שלכם יכול להיות תלוי במבנה רכיב פשוט ועקבי. השם שלך קוד העדכון לא צריך לדעת את המבנה שמשמש ברינדור. אם נביא בחשבון את התוכן שמוצג, השם יופיע שנייה באנגלית (אחרי הפקודה "Hi! " קוראים לי), אבל קודם ביפנית (לפני '申元件'). ההבחנה הזו חסרת משמעות מבחינה סמנטית מנקודת המבט של עדכון השם שמוצג, כך שקוד עדכון השם לא צריך לדעת על הפרטים האלה.

Extra Credit: תחזית מתקדמת

בדוגמה שלמעלה, הרכיב <content> בוחר את כל התוכן ממארח הצללים. באמצעות select, יש לך אפשרות לקבוע מה פרויקטים של רכיבי תוכן. אפשר גם להשתמש בכמה סוגי תוכן רכיבים.

לדוגמה, אם יש לך מסמך שמכיל את הפרטים הבאים:

<div id="nameTag">
  <div class="first">Bob</div>
  <div>B. Love</div>
  <div class="email">bob@</div>
</div>

ושורש צללית שמשתמש בסלקטורים ב-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"> הולך למסיבה הצהובה, אף אחד לא זמין להגיע למסיבה הכחולה. (יכול להיות למה היא כל כך כחולה, למרות מצוקה אוהבת חברות, אז אתם אף פעם.)

אם מזמינים משהו למסיבות לא, הוא לא יקבל מעובדת כלל. זה מה שקרה לטקסט "שלום עולם" את הדוגמה הראשונה. האפשרות הזאת שימושית כשרוצים להשיג עיבוד שונה לחלוטין: כתבו את המודל הסמנטי המסמך הנגיש לסקריפטים בדף, אבל להסתיר למטרות עיבוד ולחבר אותו מודל רינדור ב-SHAdow DOM באמצעות JavaScript.

לדוגמה, ב-HTML יש בורר תאריכים נחמד. אם כותבים <input type="date">, נוצר יומן קופץ. אבל מה אם שרוצים לאפשר למשתמש לבחור טווח תאריכים לקינוח שלו חופשה באי (את יודעת... עם ערסלים שנוצרו מ-Redines). שלך כדי להגדיר את המסמך:

<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 של Shadow 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>

עברת את שלב 101 של Shadow DOM

אלה העקרונות הבסיסיים של Shadow DOM – צריך לעבור את Shadow DOM 101! אפשר לעשות יותר עם Shadow DOM, לדוגמה, אפשר להשתמש במספר צללים מארח צל אחד או צלליות מקננות לאקפסולציה, או לארכיטקטורה את הדף באמצעות 'תצוגות מבוססות מודלים' (MDV) ו-shadow DOM. ואינטרנט רכיבים הם לא רק Shadow DOM.

אנחנו מסבירים את זה בפוסטים הבאים.