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