Shadow DOM, templates en slots

Webfuncties: Shadow DOM <slot>

Een shadow root is een ingekapselde DOM-substructuur die is gekoppeld aan een hostelement: stijlen binnen de shadow root lekken niet naar buiten en paginastijlen lekken niet naar binnen (behalve overgenomen property's). Templates bouwen de boomstructuur efficiënt op. Slots projecteren Light DOM-content erin.

Een schaduwroot toevoegen

this.attachShadow({ mode: 'open', delegatesFocus: true });

delegatesFocus: true zorgt dat de focus van de host wordt verplaatst naar het eerste focusbare element in de schaduwstructuur, stuurt :focus-stijlen naar de host en stuurt op formuliergerelateerde aangepaste elementen (static formAssociated = true) <label>-klikken door naar de interne bediening. Stel dit in als de component focusbare bedieningselementen omvat. Gebruik in bijna alle gevallen mode: 'open', zodat ontwikkelaarstools, paginascripts en serverhydratatie element.shadowRoot kunnen bereiken (zie Web Components Orientation).

Templates: de boom efficiënt samenstellen

Definieer een <template> eenmalig (modulebereik of een statisch veld) en kloon deze per instantie. Een geparseerd fragment klonen is veel sneller dan een innerHTML-tekenreeks opnieuw parseren bij elke constructie.

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

Houd templates overzichtelijk. Diep geneste 'div-soep' en veel niveaus van geneste schaduwroots voegen beide meetbare stijlresolutie en indelingskosten toe, een grote prestatie-impact op schaal. Geef de voorkeur aan een platte structuur en semantische elementen.

Slots: de delen die mensen verrassen

Standaard- en benoemde slots werken zoals gedocumenteerd. Dit is het gedrag dat problemen veroorzaakt:

  • Witruimte telt. Tekstknooppunten tussen elementen (waaronder pure witruimte) worden toegewezen aan de standaardruimte, dus een standaardruimte is zelden echt leeg.
  • De DOM-volgorde blijft behouden, niet de ruimtevolgorde. Geprojecteerde nodes behouden hun oorspronkelijke Light DOM-volgorde voor :first-child/:last-child-doeleinden, ongeacht welke sleuf ze vullen. Een ::slotted(*:first-child)-regel target het eerste onderliggende DOM-element, niet het eerste onderliggende element van die ruimte. De regel komt daarom vaak niet overeen met wat u verwacht.
  • slotchange is het signaal voor de upgradetiming. Deze wordt geactiveerd als toegewezen nodes veranderen (waaronder de eerste toewijzing) en is de betrouwbare manier om te weten dat Light DOM-onderliggende elementen zijn aangekomen na een upgrade, wanneer ze misschien niet bestonden tijdens connectedCallback.
this.shadowRoot.querySelector('slot[name="header"]')
  .addEventListener('slotchange', (e) => {
    const nodes = e.target.assignedElements();
    this.toggleAttribute('has-header', nodes.length > 0);
  });

Stylesheets laden in een shadow root

Methode Voordelen Nadelen
<style> in de template Eenvoudig, volledig ingekapseld, werkt zonder JS. Wordt per instantie gedupliceerd, waardoor het DOM op schaal wordt opgeblazen.
<link rel="stylesheet"> Extern bestand dat in het cachegeheugen kan worden opgeslagen. Extra verzoek; risico op een flits van niet-opgemaakte content.
adoptedStyleSheets Eén gedeelde CSSStyleSheet voor alle instanties; het snelst op schaal. Vereist JS; samengestelde spreadsheets ondersteunen @import niet.

Voor veel instanties van één component heeft een gedeelde samengestelde stylesheet de voorkeur:

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];
  }
}