Modello, area annuncio e ombra

Il vantaggio dei componenti web è la possibilità di essere riutilizzati: è possibile creare un widget dell'interfaccia utente una sola volta e riutilizzarlo più volte. Sebbene JavaScript sia necessario per creare componenti web, non è necessaria una libreria JavaScript. Il codice HTML e le API associate forniscono tutto ciò di cui hai bisogno.

Lo standard del componente web è costituito da tre parti: modelli HTML, elementi personalizzati e shadow DOM. Combinati, consentono di creare elementi riutilizzabili personalizzati, autonomi (incapsulati) che possono essere perfettamente integrati nelle applicazioni esistenti, come tutti gli altri elementi HTML di cui abbiamo già parlato.

In questa sezione creeremo l'elemento <star-rating>, un componente web che consente agli utenti di valutare un'esperienza su una scala da una a cinque stelle. Quando assegni un nome a un elemento personalizzato, è consigliabile utilizzare solo lettere minuscole. Includi anche un trattino, per distinguere tra elementi regolari e personalizzati.

Discuteremo dell'utilizzo degli elementi <template> e <slot>, dell'attributo slot e di JavaScript per creare un modello con un DOM Shadow incapsulato. Successivamente, riutilizzeremo l'elemento definito, personalizzando una sezione di testo, come faresti con qualsiasi elemento o componente web. Discuteremo inoltre 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. I contenuti dell'elemento non vengono visualizzati per impostazione predefinita. Piuttosto, vengono create un'istanza utilizzando 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ì, questo codepen è vuoto, ma se controlli la scheda HTML, vedrai il markup <template>.

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

Questo breve codice JavaScript non ha creato un elemento personalizzato. Piuttosto, questo esempio ha aggiunto i contenuti di <template> al <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 per una sola valutazione a stelle non è molto utile, ma è utile creare un componente web per un widget di valutazione a stelle personalizzabile e utilizzato ripetutamente.

Elemento <slot>

Includiamo un'area 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 un'"area con nome". Uno spazio denominato può essere usato per personalizzare i contenuti di un componente web. L'elemento <slot> consente di controllare dove inserire gli elementi secondari di un elemento personalizzato all'interno del relativo albero delle ombre.

Nel nostro modello, cambiamo <legend> in <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 al nome di un'area con nome. Se l'elemento personalizzato non corrisponde a un'area, verranno visualizzati i contenuti di <slot>. Pertanto, abbiamo incluso un elemento <legend> con contenuti generici che possono essere visualizzati se qualcuno include semplicemente <star-rating></star-rating>, senza contenuti, nel codice 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> all'interno di un <template>. Nel nostro elemento personalizzato, l'elemento con l'attributo area è <legend>. Non deve esserlo. 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

In <template> abbiamo usato un elemento <rating>. Questo non è un elemento personalizzato. Piuttosto, è un elemento sconosciuto. I browser non restituiscono errori quando non riconoscono un elemento. Gli elementi HTML non riconosciuti vengono trattati dal browser come elementi in linea anonimi che possono essere definiti con CSS. Analogamente a <span>, gli elementi <rating> e <star-rating> non hanno stili o semantiche applicati allo user agent.

Tieni presente che <template> e i contenuti non vengono visualizzati. <template> è un elemento noto che include contenuti che non devono essere visualizzati. L'elemento <star-rating> deve ancora essere definito. Fino a quando 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 il nostro elemento per convertire questo elemento non riconosciuto in un elemento personalizzato.

Elementi personalizzati

Per definire gli elementi personalizzati è necessario JavaScript. Una volta definiti, i contenuti dell'elemento <star-rating> vengono sostituiti da una radice di ombra contenente tutti i contenuti del modello a cui è associato. Gli elementi <slot> del modello vengono sostituiti con i contenuti dell'elemento all'interno dell'elemento <star-rating> il cui valore dell'attributo slot corrisponde al valore del nome di <slot>, se presente. In caso contrario, vengono visualizzati i contenuti degli spazi del modello.

I contenuti di un elemento personalizzato che non sono associati a un'area (<p>Is this text visible?</p> nel terzo <star-rating>) non sono inclusi nella radice shadow e pertanto non vengono visualizzati.

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>, verrà visualizzato come definito dall'elemento con #star-rating-template, che è il nostro modello. Il browser collegherà un albero DOM al nodo, aggiungendo un clone dei contenuti del modello al DOM shadow. Tieni presente che gli elementi in base ai quali 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 shadow di ogni elemento personalizzato. Un clone dei contenuti <template> è visibile in ogni elemento personalizzato negli strumenti per sviluppatori ed è visibile nel browser, ma i contenuti dell'elemento personalizzato non vengono visualizzati sullo schermo.

Screenshot di 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, aggiungendoli al DOM standard. Nella definizione di customElements, abbiamo utilizzato lo stesso appendChild(), ma i contenuti del modello clonati sono stati aggiunti a un DOM shadow incapsulato.

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

DOM shadow

Il DOM Shadow regola gli stili CSS per ogni albero delle ombre, isolandolo dal resto del documento. Ciò significa che il codice CSS esterno non viene applicato al componente e che gli stili dei componenti non hanno effetto sul resto del documento, a meno che non vengano reindirizzati intenzionalmente.

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

Essendo limitato all'elemento personalizzato, non dobbiamo preoccuparci degli stili che emergono dal resto del documento. Possiamo ridurre sostanzialmente la specificità dei selettori. Ad esempio, poiché 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 il DOM shadow e sono nascosti da tutti gli elementi al di fuori dei componenti, i contenuti dell'area che vengono visualizzati, la porzione <anyElement slot="star-rating-legend"> di <star-rating>, non sono incapsulati.

Stile al di fuori dell'ambito corrente

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

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

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

Lo pseudo-elemento ::slotted() attraversa il confine del DOM shadow all'interno del 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, il modello deve essere modificato. L'attributo part può essere aggiunto a qualsiasi elemento a cui vuoi applicare uno stile. Usa poi lo pseudo elemento ::part() per associare gli elementi di un albero di ombreggiatura che corrispondono al parametro passato. L'ancora o l'elemento di origine per lo pseudo-elemento è l'host, o nome dell'elemento personalizzato, in questo caso star-rating. Il parametro è il valore dell'attributo part.

Se il markup del nostro 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 avere lo stesso nome di parte.

Google offre un fantastico elenco di controllo per creare elementi personalizzati. Ti consigliamo di scoprire di più sui DOM shadow dichiarative.

Verifica la tua comprensione

Verifica le tue conoscenze su modello, area annuncio e shadow.

Per impostazione predefinita, gli stili esterni allo shadow DOM applicano uno stile agli elementi all'interno.

Vero
Riprova.
Falso
risposta esatta.

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

Un elemento generico utilizzato per visualizzare i contenuti della pagina.
Riprova.
Un elemento segnaposto.
Riprova.
Un elemento utilizzato per dichiarare frammenti di HTML che non verranno visualizzati per impostazione predefinita.
risposta esatta.