Ein Shadow-Root ist ein gekapselter DOM-Unterbaum, der an ein Hostelement angehängt ist. Stile innerhalb des Shadow-Roots dringen nicht nach außen und Seitenstile dringen nicht nach innen (außer geerbte Eigenschaften). Vorlagen erstellen den Baum effizient, Slots projizieren Light-DOM-Inhalte hinein.
Shadow-Root anhängen
this.attachShadow({ mode: 'open', delegatesFocus: true });
delegatesFocus: true sorgt dafür, dass der Fokus beim Fokussieren des Hosts auf das erste fokussierbare Element im Shadow Tree verschoben wird, leitet das :focus-Styling an den Host weiter und leitet bei formularbezogenen benutzerdefinierten Elementen (static formAssociated = true) <label>-Klicks an das innere Steuerelement weiter. Legen Sie es immer fest, wenn die Komponente fokussierbare Steuerelemente umschließt. Verwenden Sie in fast allen Fällen mode: 'open', damit Entwicklertools, Seitenskripts und Server-Hydration element.shadowRoot erreichen können (siehe Web Components Orientation).
Vorlagen: Baumstruktur effizient erstellen
Definieren Sie einen <template> einmal (Modulbereich oder statisches Feld) und klonen Sie ihn pro Instanz. Das Klonen eines geparsten Fragments ist viel schneller als das erneute Parsen eines innerHTML-Strings bei jeder Erstellung.
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);
Halten Sie Vorlagen schlank. Tief verschachtelte „div-Suppe“ und viele Ebenen verschachtelter Shadow-Roots verursachen messbare Kosten für die Stilauflösung und das Layout, was bei großen Projekten zu erheblichen Leistungseinbußen führt. Bevorzugen Sie eine flache Struktur und semantische Elemente.
Slots: die Teile, die die Leute überraschen
Standard- und benannte Slots funktionieren wie dokumentiert. Die folgenden Verhaltensweisen sind jedoch problematisch:
- Leerzeichen zählen. Textknoten zwischen Elementen (einschließlich reiner Leerzeichen) werden dem Standard-Slot zugewiesen. Ein Standard-Slot ist daher selten wirklich „leer“.
- Die DOM-Reihenfolge wird beibehalten, nicht die Slot-Reihenfolge. Projizierte Knoten behalten ihre ursprüngliche Light-DOM-Reihenfolge für
:first-child/:last-childbei, unabhängig davon, welchen Slot sie füllen. Eine::slotted(*:first-child)-Regel ist auf das erste DOM-Unterelement ausgerichtet, nicht auf das erste Unterelement dieser Anzeigenfläche. Daher entspricht sie oft nicht Ihren Erwartungen. slotchangeist das Signal für den Upgrade-Zeitpunkt. Es wird ausgelöst, wenn sich zugewiesene Knoten ändern (einschließlich der ersten Zuweisung). Es ist die zuverlässige Methode, um festzustellen, ob Light DOM-Unterelemente nach einem Upgrade vorhanden sind, wenn sie währendconnectedCallbackmöglicherweise nicht vorhanden waren.
this.shadowRoot.querySelector('slot[name="header"]')
.addEventListener('slotchange', (e) => {
const nodes = e.target.assignedElements();
this.toggleAttribute('has-header', nodes.length > 0);
});
Stylesheets in einen Shadow-Root laden
| Methode | Vorteile | Nachteile |
|---|---|---|
<style> in der Vorlage |
Einfach, vollständig gekapselt, funktioniert ohne JS. | Wird pro Instanz dupliziert und bläht das DOM bei großen Mengen auf. |
<link rel="stylesheet"> |
Cache-fähige externe Datei. | Zusätzliche Anfrage; Risiko, dass Inhalte kurzzeitig ohne Formatierung angezeigt werden. |
adoptedStyleSheets |
Eine freigegebene CSSStyleSheet für alle Instanzen; am schnellsten bei der Skalierung. |
Erfordert JS; konstruierte Tabellen unterstützen @import nicht. |
Wenn Sie viele Instanzen einer Komponente haben, sollten Sie ein freigegebenes konstruiertes Stylesheet verwenden:
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];
}
}