Modello, area annuncio e ombra

Il vantaggio dei componenti web è la loro riutilizzabilità: puoi creare un widget dell'interfaccia utente una volta e riutilizzarlo più volte. Mentre serve JavaScript per creare componenti web, non hai bisogno di una libreria JavaScript. Il codice HTML e le API associate forniscono tutto ciò di cui hai bisogno.

Lo standard del componente web è composto da tre parti: modelli HTML, Elementi personalizzati e Shadow DOM. Se combinati, consentono di creare elementi personalizzati, autonomi (incapsulati) e riutilizzabili che possono essere perfettamente integrati in applicazioni esistenti, come tutti gli altri elementi HTML che abbiamo già illustrato.

In questa sezione creeremo l'elemento <star-rating>, un componente web che consente agli utenti di valutare un'esperienza su una con una scala da una a cinque stelle. Quando si assegna un nome a un elemento personalizzato, è consigliabile utilizzare solo lettere minuscole. Includi anche un trattino, in quanto aiuta a distinguere gli elementi regolari da quelli personalizzati.

Discuteremo dell'utilizzo degli elementi <template> e <slot>, dell'attributo slot e di JavaScript per creare un modello con un Shadow DOM incapsulato. Dopodiché riutilizzeremo l'elemento definito, personalizzando una sezione di testo, come faresti con qualsiasi elemento o componente web. Parleremo anche brevemente dell'utilizzo di CSS dall'interno e dall'esterno dell'elemento personalizzato.

Elemento <template>

L'elemento <template> viene utilizzato per dichiarare frammenti di HTML da clonare e inserire nel DOM con JavaScript. Per impostazione predefinita, il rendering dei contenuti dell'elemento non viene eseguito. Vengono invece create le istanze tramite JavaScript.

<template id="star-rating-template">
  <form>
    <fieldset>
      <legend>Rate your experience:</legend>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required />
        <input type="radio" name="rating" value="2" aria-label="2 stars" />
        <input type="radio" name="rating" value="3" aria-label="3 stars" />
        <input type="radio" name="rating" value="4" aria-label="4 stars" />
        <input type="radio" name="rating" value="5" aria-label="5 stars" />
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

Poiché i contenuti di un elemento <template> non vengono scritti sullo schermo, <form> e i suoi contenuti non vengono visualizzati. Sì, il codepen è vuoto, ma controlli la scheda HTML per vedere il markup <template>.

In questo esempio, <form> non è un elemento secondario di <template> nel DOM. I contenuti degli elementi <template> sono invece secondari di DocumentFragment restituito da HTMLTemplateElement.content proprietà. Affinché sia visibile, è necessario utilizzare JavaScript per recuperare i contenuti e aggiungerli al DOM.

Questo breve codice JavaScript non ha creato un elemento personalizzato. In questo esempio, invece, sono stati aggiunti i contenuti di <template> alla sezione <body>. I contenuti sono diventati parte del DOM visibile e modificabile.

Uno screenshot del codepen precedente, come mostrato nel DOM.

La richiesta di JavaScript per implementare un modello solo per una valutazione a stelle non è molto utile, ma la creazione di un componente web per un il widget di valutazione a stelle personalizzabile usato più volte.

Elemento <slot>

Includiamo uno spazio per includere una legenda personalizzata per occorrenza. Il codice HTML fornisce un elemento <slot> come segnaposto all'interno di un elemento <template> che, se viene fornito un nome, crea uno "spazio con nome". È possibile usare uno slot denominato per personalizzare i contenuti all'interno di un componente web. L'elemento <slot> ci offre un modo per controllare dove gli elementi secondari di un account deve essere inserito all'interno del relativo albero ombra.

Nel nostro modello, sostituisci <legend> con <slot>:

<template id="star-rating-template">
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>

L'attributo name viene utilizzato per assegnare aree ad altri elementi se l'elemento ha un attributo slot il cui valore corrisponde a il nome di un'area denominata. Se l'elemento personalizzato non corrisponde a un'area, verrà eseguito il rendering dei contenuti di <slot>. Abbiamo quindi incluso un elemento <legend> con contenuti generici che va bene per il rendering se un utente include semplicemente <star-rating></star-rating>, senza contenuti, nel proprio HTML.

<star-rating>
  <legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Toasty McToastface</legend>
  <p>Is this text visible?</p>
</star-rating>

L'attributo slot è un attributo globale utilizzato per sostituire i contenuti di <slot> in un <template>. Nel nostro elemento personalizzato, l'elemento con l'attributo slot è un <legend>. Non è necessario. Nel nostro modello, <slot name="star-rating-legend"> verrà sostituito con <anyElement slot="star-rating-legend">, dove <anyElement> può essere qualsiasi elemento, anche un altro elemento personalizzato.

Elementi non definiti

Nel nostro <template> abbiamo utilizzato un elemento <rating>. Questo non è un elemento personalizzato. Piuttosto, è un elemento sconosciuto. Browser se non riconoscono un elemento. Gli elementi HTML non riconosciuti vengono trattati dal browser come incorporati anonimi a cui è possibile definire uno stile. Analogamente a <span>, agli elementi <rating> e <star-rating> non è applicato alcun user agent stili o semantica.

Tieni presente che <template> e i contenuti non vengono visualizzati. <template> è un elemento noto che include contenuti che da non eseguire il rendering. L'elemento <star-rating> deve ancora essere definito. Finché non definiamo un elemento, il browser lo visualizza come tutti gli elementi non riconosciuti. Per il momento, l'elemento <star-rating> non riconosciuto viene considerato come un elemento incorporato anonimo, quindi i contenuti incluse le legende e il <p> nel terzo <star-rating> vengono visualizzati come se fossero invece in un <span>.

Definiamo l'elemento per convertire questo elemento non riconosciuto in un elemento personalizzato.

Elementi personalizzati

Per definire elementi personalizzati è necessario JavaScript. Se definito, i contenuti dell'elemento <star-rating> verranno sostituiti da un radice shadow contenente tutti i contenuti del modello che vi associamo. Gli elementi <slot> del modello vengono sostituiti con i contenuti dell'elemento all'interno di <star-rating> il cui valore dell'attributo slot corrisponde al valore del nome di <slot>, se Eccone una. In caso contrario, vengono visualizzati i contenuti degli slot del modello.

I contenuti all'interno di un elemento personalizzato non associato a un'area, ovvero il <p>Is this text visible?</p> nel nostro terzo <star-rating>, non sono inclusi in nella radice ombra e pertanto non viene visualizzata.

Noi definiamo l'elemento personalizzato denominato star-rating estendendo HTMLElement:

customElements.define('star-rating',
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const starRating = document.getElementById('star-rating-template').content;
      const shadowRoot = this.attachShadow({
        mode: 'open'
      });
      shadowRoot.appendChild(starRating.cloneNode(true));
    }
  });

Ora che l'elemento è stato definito, ogni volta che il browser rileva un elemento <star-rating>, viene eseguito come definito dall'elemento con #star-rating-template, che è il nostro modello. Il browser collegherà al nodo un albero DOM shadow, aggiungendo un clone dei contenuti del modello in quel DOM shadow. Tieni presente che gli elementi su cui puoi attachShadow() sono limitati.

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(starRating.cloneNode(true));

Se dai un'occhiata agli strumenti per sviluppatori, noterai che <form> di <template> fa parte della radice ombra di ogni elemento personalizzato. Un clone dei contenuti <template> è evidente in ogni elemento personalizzato negli strumenti per sviluppatori ed è visibile nel browser, ma i contenuti non viene visualizzato sullo schermo.

Screenshot DevTools che mostra i contenuti dei modelli clonati in ogni elemento personalizzato.

Nell'esempio <template>, abbiamo aggiunto i contenuti del modello al corpo del documento, aggiungendo i contenuti al DOM normale. Nella definizione di customElements abbiamo utilizzato la stessa appendChild(), ma i contenuti del modello clonato sono stati aggiunti a un shadow incapsulato.

Hai notato come le stelle sono tornate a essere pulsanti di opzione senza stile? Poiché fanno parte di uno shadow DOM anziché del DOM standard, lo stile all'interno della scheda CSS di Codepen non viene applicato. Il CSS della scheda Gli stili hanno come ambito il documento, non il DOM shadow, per cui non vengono applicati. Dobbiamo creare modelli per definire i nostri contenuti incapsulati del DOM Shadow.

DOM shadow

Il DOM shadow limita gli stili CSS a ogni albero shadow, isolandoli dal resto del documento. Questo significa che il CSS esterno non si applica al tuo componente e gli stili dei componenti non hanno effetto sul resto del documento, a meno che non venga intenzionalmente indirizzarli a.

Poiché abbiamo aggiunto i contenuti a un DOM shadow, possiamo includere un elemento <style> fornendo CSS incapsulato all'elemento personalizzato.

Poiché l'ambito è l'elemento personalizzato, non dobbiamo preoccuparci che gli stili vengano visualizzati nel resto del documento. Possiamo ridurre in modo sostanziale la specificità dei selettori. Ad esempio, gli unici input utilizzati nell'elemento personalizzato sono i pulsanti di opzione possiamo usare input anziché input[type="radio"] come selettore.

 <template id="star-rating-template">
  <style>
    rating {
      display: inline-flex;
    }
    input {
      appearance: none;
      margin: 0;
      box-shadow: none;
    }
    input::after {
      content: '\2605'; /* solid star */
      font-size: 32px;
    }
    rating:hover input:invalid::after,
    rating:focus-within input:invalid::after {
      color: #888;
    }
    input:invalid::after,
      rating:hover input:hover ~ input:invalid::after,
      input:focus ~ input:invalid::after  {
      color: #ddd;
    }
    input:valid {
      color: orange;
    }
    input:checked ~ input:not(:checked)::after {
      color: #ccc;
      content: '\2606'; /* hollow star */
    }
  </style>
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required/>
        <input type="radio" name="rating" value="2" aria-label="2 stars"/>
        <input type="radio" name="rating" value="3" aria-label="3 stars"/>
        <input type="radio" name="rating" value="4" aria-label="4 stars"/>
        <input type="radio" name="rating" value="5" aria-label="5 stars"/>
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

Mentre i componenti web sono incapsulati con il markup in-<template> e gli stili CSS hanno come ambito lo shadow DOM e sono nascosti da tutto ciò che è esterno ai componenti, dai contenuti degli slot che vengono visualizzati, <anyElement slot="star-rating-legend"> porzione di <star-rating>, non è incapsulato.

Stili che non rientrano nell'ambito attuale

È possibile, ma non semplice, applicare uno stile al documento dall'interno di un DOM shadow e ai contenuti di un DOM shadow gli stili globali. Il confine ombra, dove termina il DOM ombra e inizia il DOM regolare, può essere attraversato, ma molto intenzionalmente.

L'albero ombra è l'albero DOM all'interno del DOM shadow. La radice ombra è il nodo radice dell'albero ombra.

La pseudo-classe :host seleziona <star-rating>, l'elemento host shadow. L'host shadow è il nodo DOM a cui è collegato il DOM shadow. Per scegliere come target solo versioni specifiche dell'host, utilizza :host(). Verranno selezionati solo gli elementi host shadow che corrispondono al parametro passato, come un selettore di classe o attributo. Per selezionare tutti gli elementi personalizzati, puoi usare star-rating { /* styles */ } nel file CSS globale o :host(:not(#nonExistantId)) negli stili del modello. In termini di specificità, vince il CSS globale.

Lo pseudoelemento ::slotted() attraversa il confine DOM ombra dal DOM shadow. Seleziona un elemento con slot se corrisponde al selettore. Nel nostro esempio, ::slotted(legend) corrisponde alle nostre tre legende.

Per scegliere come target un DOM shadow da CSS nell'ambito globale, è necessario modificare il modello. La part può essere aggiunto a qualsiasi elemento a cui vuoi applicare uno stile. Quindi utilizza lo pseudo-elemento ::part() per abbinare gli elementi all'interno di un albero ombra che corrispondono al parametro passato. L'elemento di ancoraggio o di origine dello pseudo-elemento è l'host o il nome dell'elemento personalizzato, in questo caso star-rating. Il parametro è il valore dell'attributo part.

Se il markup del modello è iniziato in questo modo:

<template id="star-rating-template">
  <form part="formPart">
    <fieldset part="fieldsetPart">

Potremmo scegliere come target <form> e <fieldset> con:

star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }

I nomi delle parti funzionano in modo simile alle classi: un elemento può avere più nomi di parti separati da spazi e più elementi possono hanno lo stesso nome componente.

Google ha un elenco di controllo molto utile per creare elementi personalizzati. Inoltre, potrebbero essere utili sui DOM shadow dichiarativi.

Verifica le tue conoscenze

Verifica le tue conoscenze in materia di modello, area e ombra.

Per impostazione predefinita, gli stili esterni al DOM shadow definiscono gli elementi all'interno.

Vero
Riprova.
Falso.
Esatto!

Quale risposta è una descrizione corretta dell'elemento <template>?

Un elemento generico utilizzato per visualizzare i contenuti della pagina.
Riprova.
Un elemento segnaposto.
Riprova.
Elemento utilizzato per dichiarare frammenti di HTML che non verrà visualizzato per impostazione predefinita.
Esatto!