Shadow DOM, modelli e slot

Funzionalità web: Shadow DOM <slot>

Una shadow root è un sottoalbero DOM incapsulato collegato a un elemento host: gli stili al suo interno non vengono applicati all'esterno e gli stili della pagina non vengono applicati all'interno (ad eccezione delle proprietà ereditate). I template creano l'albero in modo efficiente; gli slot proiettano i contenuti Light DOM al suo interno.

Collegamento di una shadow root

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

delegatesFocus: true fa sì che lo stato attivo dell'host si sposti sul primo elemento attivabile all'interno dello shadow tree, indirizza lo stile :focus all'host e, sugli elementi personalizzati associati al modulo (static formAssociated = true), inoltra i clic <label> al controllo interno. Impostalo ogni volta che il componente racchiude controlli attivabili. Utilizza mode: 'open' in quasi tutti i casi, in modo che gli strumenti di sviluppo, gli script di pagina e l'idratazione del server possano raggiungere element.shadowRoot (consulta Orientamento ai componenti web).

Modelli: costruisci l'albero in modo efficiente

Definisci un <template> una volta (ambito del modulo o un campo statico) e clonalo per istanza. La clonazione di un frammento analizzato è molto più veloce della nuova analisi di una stringa innerHTML a ogni costruzione.

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

Mantieni i modelli snelli. La "zuppa di div" con nidificazione profonda e molti livelli di shadow root nidificati aggiungono un costo misurabile per la risoluzione dello stile e il layout, un impatto significativo sulle prestazioni su larga scala. Preferisci una struttura piatta ed elementi semantici.

Slot: le parti che sorprendono le persone

Gli slot predefiniti e quelli con nome funzionano come documentato; questi sono i comportamenti che creano problemi:

  • Gli spazi vuoti sono importanti. I nodi di testo tra gli elementi (inclusi gli spazi vuoti puri) vengono assegnati allo slot predefinito, quindi uno slot predefinito è raramente "vuoto".
  • Viene conservato l'ordine del DOM, non quello degli slot. I nodi proiettati mantengono l'ordine Light DOM originale per gli scopi di :first-child/:last-child, indipendentemente dallo slot che occupano. Una regola ::slotted(*:first-child) ha come target il primo elemento secondario DOM, non il primo elemento secondario di quell'area, quindi spesso non corrisponde a ciò che ti aspetti.
  • slotchange è l'indicatore della tempistica di upgrade. Viene attivato quando i nodi assegnati cambiano (inclusa l'assegnazione iniziale) ed è il modo affidabile per sapere che gli elementi secondari del Light DOM sono arrivati dopo un upgrade, quando potrebbero non essere esistiti durante connectedCallback.
this.shadowRoot.querySelector('slot[name="header"]')
  .addEventListener('slotchange', (e) => {
    const nodes = e.target.assignedElements();
    this.toggleAttribute('has-header', nodes.length > 0);
  });

Caricamento dei fogli di stile in una shadow root

Metodo Pro Contro
<style> nel template Semplice, completamente incapsulato, funziona senza JS. Duplicato per istanza; gonfia il DOM su larga scala.
<link rel="stylesheet"> File esterno memorizzabile nella cache. Richiesta aggiuntiva; rischio di un flash di contenuti non formattati.
adoptedStyleSheets Un CSSStyleSheet condiviso tra tutte le istanze; il più veloce su larga scala. Richiede JS; i fogli costruiti non supportano @import.

Per molte istanze di un componente, preferisci un foglio di stile costruito condiviso:

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