שורש צל הוא עץ משנה של DOM שמצורף לרכיב מארח: סגנונות בתוכו לא דולפים החוצה, וסגנונות של דפים לא דולפים פנימה (למעט מאפיינים שעוברים בירושה). התבניות בונות את העץ ביעילות, והמשבצות מקרינות לתוכו תוכן של Light DOM.
צירוף של shadow root
this.attachShadow({ mode: 'open', delegatesFocus: true });
delegatesFocus: true גורם לכך שהמיקוד במארח יעביר את המיקוד לרכיב הראשון שניתן להתמקד בו בתוך עץ הצל, מנתב את הסגנון של :focus למארח, וברכיבים מותאמים אישית שמשויכים לטופס (static formAssociated = true) מעביר קליקים על <label> לפקד הפנימי. צריך להגדיר אותו בכל פעם שהרכיב כולל פקדים שאפשר להתמקד בהם. כמעט בכל המקרים, צריך להשתמש ב-mode: 'open' כדי שכלי פיתוח, סקריפטים של דפים והידרציה של שרתים יוכלו להגיע אל element.shadowRoot (ראו התמצאות ברכיבי אינטרנט).
תבניות: בניית העץ ביעילות
מגדירים <template> פעם אחת (היקף המודול או שדה סטטי) ומשכפלים אותו לכל מכונה. שכפול של קטע מנותח מהיר בהרבה מניתוח מחדש של מחרוזת innerHTML בכל בנייה.
const template = document.createElement('template');
template.innerHTML = `
<div class="card">
<slot name="header"></slot>
<div class="body"><slot></slot></div>
<slot name="footer"></slot>
</div>
`;
class MyCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// MANDATORY: deep-clone the template content so each instance gets its own nodes.
this.shadowRoot.append(template.content.cloneNode(true));
}
}
customElements.define('my-card', MyCard);
התבניות צריכות להיות פשוטות. מבנה div soup עם רמות רבות של קינון ורמות רבות של שורשי צל מקוננים מוסיפים עלות מדידה של רזולוציית סגנון ופריסה, ופוגעים באופן משמעותי בביצועים בקנה מידה גדול. עדיף להשתמש במבנה שטוח וברכיבים סמנטיים.
משבצות: החלקים שמפתיעים אנשים
ההבדל בין מיקומים מוגדרים מראש למיקומים עם שם פועל כמו שמתואר במסמכים. אלה ההתנהגויות שעלולות לגרום לבעיות:
- רווחים נספרים. צומתי טקסט בין רכיבים (כולל רווחים לבנים בלבד) מוקצים למיקום ברירת המחדל, כך שמיקום ברירת המחדל הוא לעיתים רחוקות "ריק" באמת.
- סדר ה-DOM נשמר, לא סדר המשבצות. צמתים מוקרנים שומרים על הסדר המקורי שלהם ב-Light DOM למטרות
:first-child/:last-child, בלי קשר למיקום שהם ממלאים. כלל::slotted(*:first-child)מטרגט את הצאצא הראשון של DOM, ולא את הצאצא הראשון של המיקום הזה, ולכן לעיתים קרובות הוא לא יתאים למה שאתם מצפים. slotchangeהוא האות לתזמון השדרוג. הוא מופעל כשצמתים שהוקצו משתנים (כולל הקצאה ראשונית) וזו הדרך האמינה לדעת שצאצאי Light DOM הגיעו אחרי שדרוג, כשהם לא היו קיימים במהלךconnectedCallback.
this.shadowRoot.querySelector('slot[name="header"]')
.addEventListener('slotchange', (e) => {
const nodes = e.target.assignedElements();
this.toggleAttribute('has-header', nodes.length > 0);
});
טעינת גיליונות סגנונות לתוך שורש צל (shadow root)
| שיטה | יתרונות | חסרונות |
|---|---|---|
<style> בתבנית |
פשוט, מובנה לחלוטין, פועל בלי JS. | משוכפל בכל מופע, מגדיל את ה-DOM בקנה מידה גדול. |
<link rel="stylesheet"> |
קובץ חיצוני שניתן לשמור במטמון. | בקשה נוספת; סיכון להבזק של תוכן לא מעוצב. |
adoptedStyleSheets |
CSSStyleSheet אחד משותף לכל המכונות, הכי מהיר בקנה מידה גדול. |
נדרש JS; גיליונות מובנים לא תומכים ב-@import. |
אם יש הרבה מופעים של רכיב אחד, עדיף להשתמש בגיליון סגנונות משותף:
const sheet = new CSSStyleSheet();
sheet.replaceSync(`:host { display: block; }`);
class MyCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// The same sheet object is reused by every instance; no per-instance parse.
this.shadowRoot.adoptedStyleSheets = [sheet];
}
}