Modèle, emplacement et ombre

Les composants Web présentent l'avantage de pouvoir être réutilisés: vous pouvez créer un widget d'interface utilisateur une seule fois et le réutiliser plusieurs fois. Pendant que vous vous avez besoin de JavaScript pour créer des composants Web, vous n'avez pas besoin d'une bibliothèque JavaScript. HTML et les API associées fournissent tout ce dont vous avez besoin.

Les composants Web standards se composent de trois parties : les modèles HTML, les éléments personnalisés et le Shadow DOM. Ensemble, ils permettent de créer des éléments personnalisés, autonomes (encapsulés et réutilisables) qui peuvent être intégrés facilement dans des applications existantes, comme tous les autres éléments HTML que nous avons déjà abordés.

Dans cette section, nous allons créer l'élément <star-rating>, un composant Web qui permet aux utilisateurs d'évaluer une expérience sur une sur une échelle de une à cinq étoiles. Lorsque vous nommez un élément personnalisé, nous vous recommandons d'utiliser uniquement des lettres minuscules. Incluez également un tiret, car cela permet de distinguer les éléments standards des éléments personnalisés.

Nous allons voir comment utiliser les éléments <template> et <slot>, l'attribut slot et JavaScript pour créer un modèle avec un Shadow DOM encapsulé. Nous allons ensuite réutiliser l'élément défini pour personnaliser une section de texte, comme vous le feriez pour n'importe quel élément ou composant Web. Nous aborderons également brièvement l'utilisation du CSS à l'intérieur et à l'extérieur de l'élément personnalisé.

L'élément <template>

L'élément <template> permet de déclarer des fragments de HTML à cloner et à insérer dans le DOM avec JavaScript. Le contenu de l'élément n'est pas affiché par défaut. Elles sont plutôt instanciées à l'aide de 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>

Comme le contenu d'un élément <template> n'est pas écrit à l'écran, <form> et son contenu ne sont pas affichés. Oui, ce Codepen est vide, mais si vous inspectez l'onglet HTML, vous verrez le balisage <template>.

Dans cet exemple, le <form> n'est pas un enfant d'un <template> dans le DOM. Le contenu des éléments <template> est plutôt des enfants d'un élément DocumentFragment renvoyé par le HTMLTemplateElement.content . Pour que le contenu soit rendu visible, JavaScript doit être utilisé afin de saisir le contenu et de l'ajouter au DOM.

Ce fichier JavaScript rapide n'a pas créé d'élément personnalisé. À la place, cet exemple a ajouté le contenu de <template> à <body>. Le contenu fait maintenant partie du DOM visible et stylable.

Capture d&#39;écran du codepen précédent, comme indiqué dans le DOM

Exiger JavaScript pour implémenter un modèle pour une seule note n'est pas très utile, mais créer un composant Web pour une utilisé de façon répétée, le widget personnalisable d'évaluation des notes est utile.

L'élément <slot>

Nous incluons un emplacement pour inclure une légende personnalisée par occurrence. HTML fournit un élément <slot> en tant qu'espace réservé dans un élément <template> qui, si un nom est fourni, crée un "emplacement nommé". Un emplacement nommé peut être utilisé pour personnaliser le contenu d'un composant Web. L'élément <slot> permet de contrôler l'emplacement des enfants d'une doit être inséré dans son arborescence ombre.

Dans notre modèle, nous remplaçons <legend> par <slot>:

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

L'attribut name permet d'attribuer des emplacements à d'autres éléments si l'élément possède un attribut slot dont la valeur correspond à la nom d'un emplacement nommé. Si l'élément personnalisé ne trouve aucune correspondance pour un espace publicitaire, le contenu de <slot> est affiché. Nous avons donc inclus un <legend> avec du contenu générique qui peut être affiché si quelqu'un inclut simplement <star-rating></star-rating>, sans contenu, dans son code 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'attribut slot est un attribut global utilisé pour remplacer le contenu de <slot> dans un <template>. Dans notre élément personnalisé, l'élément comportant l'attribut d'emplacement a le statut <legend>. Ce n'est pas nécessaire. Dans notre modèle, <slot name="star-rating-legend"> sera remplacé par <anyElement slot="star-rating-legend">, où <anyElement> peut être n'importe quel élément, voire un autre élément personnalisé.

Éléments non définis

Dans notre <template>, nous avons utilisé un élément <rating>. Ce n'est pas un élément personnalisé. Il s'agit plutôt d'un élément inconnu. Navigateurs n’échouent pas lorsqu’ils ne reconnaissent pas un élément. Les éléments HTML non reconnus sont traités par le navigateur comme des éléments intégrés anonymes. qui peuvent être stylisés avec du code CSS. Comme pour <span>, aucun user-agent n'est appliqué aux éléments <rating> et <star-rating> ou la sémantique.

Notez que <template> et son contenu ne sont pas affichés. <template> est un élément connu dont le contenu est ne doit pas être affichée. L'élément <star-rating> n'a pas encore été défini. Tant que nous n'avons pas défini un élément, le navigateur l'affiche comme tous les éléments non reconnus. Pour le moment, le <star-rating> non reconnu est traité comme un élément intégré anonyme. Le contenu y compris les légendes et le <p> dans le troisième <star-rating> s'affichent comme s'ils se trouvaient dans un <span>.

Définissons notre élément pour convertir cet élément non reconnu en élément personnalisé.

Éléments personnalisés

Vous avez besoin de JavaScript pour définir des éléments personnalisés. Lorsqu'il est défini, le contenu de l'élément <star-rating> est remplacé par un racine fantôme contenant tout le contenu du modèle que nous lui associons. Les éléments <slot> du modèle sont remplacés par le contenu de l'élément situé dans <star-rating> dont la valeur de l'attribut slot correspond à la valeur du nom de <slot>, si il y en a une. Si ce n'est pas le cas, le contenu des emplacements du modèle s'affiche.

Le contenu d'un élément personnalisé qui n'est pas associé à un emplacement (le <p>Is this text visible?</p> dans notre troisième <star-rating>) n'est pas inclus dans la racine fantôme et ne s'affiche donc pas.

Nous définissons l'élément personnalisé nommé star-rating. en étendant 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));
    }
  });

Maintenant que l'élément est défini, chaque fois que le navigateur rencontre un élément <star-rating>, il est affiché comme défini. par l'élément avec #star-rating-template, qui est notre modèle. Le navigateur associe une arborescence Shadow DOM au nœud, en ajoutant un clone du contenu du modèle dans ce Shadow DOM. Notez que les éléments sur lesquels vous pouvez attachShadow() sont limités.

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

Si vous examinez les outils pour les développeurs, vous remarquerez que le <form> de <template> fait partie de la racine fantôme de chaque élément personnalisé. Un clone du contenu de <template> est visible dans chaque élément personnalisé dans les outils pour les développeurs et visible dans le navigateur, mais le contenu de l'élément personnalisé lui-même ne sont pas affichés à l'écran.

Capture d&#39;écran des outils de développement montrant le contenu du modèle cloné dans chaque élément personnalisé.

Dans l'exemple <template>, nous avons ajouté le contenu du modèle au corps du document, en ajoutant le contenu au DOM standard. Dans la définition de customElements, nous avons utilisé la même appendChild(), mais le contenu du modèle cloné a été ajouté à une Shadow DOM encapsulé.

Avez-vous remarqué que les étoiles ont repris le style des cases d'option ? Étant donné qu'il fait partie d'un Shadow DOM plutôt que du DOM standard, le style dans l'onglet CSS de Codepen ne s'applique pas. Le CSS de cet onglet s'appliquent au document, et non au Shadow DOM. Ils ne sont donc pas appliqués. Nous devons créer des ressources pour appliquer un style au contenu Shadow DOM encapsulé.

Shadow DOM

Le Shadow DOM applique un style CSS à chaque arborescence fantôme, qui les isole du reste du document. Cela signifie que les fichiers CSS externes ne s'applique pas à votre composant et les styles de composant n'ont aucun effet sur le reste du document, sauf si vers lesquelles ils sont dirigés.

Comme nous avons ajouté le contenu à un Shadow DOM, nous pouvons inclure un élément <style>. En fournissant du code CSS encapsulé à l'élément personnalisé.

Étant donné que nous nous limitons à l'élément personnalisé, nous n'avons pas à nous soucier des styles qui apparaissent dans le reste du document. Nous pouvons réduisent considérablement la spécificité des sélecteurs. Par exemple, comme les seules entrées utilisées dans l'élément personnalisé sont des cases d'option nous pouvons utiliser input au lieu de input[type="radio"] comme sélecteur.

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

Alors que les composants Web sont encapsulés avec le balisage in-<template> et que les styles CSS sont limités au Shadow DOM et sont masqués depuis tout ce qui se trouve en dehors des composants, le contenu de l'emplacement affiché, le <anyElement slot="star-rating-legend"> de <star-rating>, n'est pas encapsulée.

Style en dehors du champ d'application actuel

Il est possible, mais pas simple, de styliser le document à partir d'un Shadow DOM et celui du contenu d'un Shadow DOM à partir de les styles globaux. La limite d'ombre, où se termine le Shadow DOM et où commence le DOM standard, peut être traversée, mais très intentionnellement.

L'arborescence Shadow est l'arborescence DOM à l'intérieur du Shadow DOM. La racine fantôme est le nœud racine de l'arborescence fantôme.

La pseudo-classe :host sélectionne <star-rating>, l'élément hôte fantôme. L'hôte fantôme est le nœud DOM auquel le Shadow DOM est associé. Pour ne cibler que des versions spécifiques de l'hôte, utilisez :host(). Cela permet de sélectionner uniquement les éléments hôtes fantômes qui correspondent au paramètre transmis, comme un sélecteur de classe ou d'attribut. Pour sélectionner tous les éléments personnalisés, vous pouvez utiliser star-rating { /* styles */ } dans le CSS global ou :host(:not(#nonExistantId)) dans les styles de modèle. En termes de spécificité, le CSS mondial l'emporte.

Le pseudo-élément ::slotted() traverse la limite du Shadow DOM depuis le Shadow DOM. Il sélectionne un élément positionné s'il correspond au sélecteur. Dans notre exemple, ::slotted(legend) correspond à nos trois légendes.

Pour cibler un Shadow DOM à partir du CSS dans le champ d'application global, le modèle doit être modifié. part peut être ajouté à tout élément auquel vous souhaitez appliquer un style. Utilisez ensuite le pseudo-élément ::part() pour faire correspondre les éléments d'une arborescence fantôme qui correspondent au paramètre transmis. L'élément d'ancrage ou d'origine du pseudo-élément est le nom de l'hôte ou de l'élément personnalisé, dans ce cas star-rating. Il correspond à la valeur de l'attribut part.

Si notre balisage de modèle a commencé comme suit:

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

Nous pourrions cibler <form> et <fieldset> avec:

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

Les noms de partie se comportent de la même manière que les classes: un élément peut avoir plusieurs noms de partie séparés par un espace et plusieurs éléments peuvent ont le même nom de pièce.

Google propose une fantastique checklist pour la création d'éléments personnalisés. Vous voudrez peut-être également apprendre sur les Shadow DOM déclaratifs.

Testez vos connaissances

Testez vos connaissances sur les modèles, les emplacements et les ombres.

Par défaut, les styles extérieurs au Shadow DOM appliquent un style aux éléments qui se trouvent à l'intérieur.

Vrai
Réessayez.
Faux
Bonne réponse !

Quelle réponse est une description correcte de l'élément <template> ?

Élément générique permettant d'afficher n'importe quel contenu sur votre page.
Réessayez.
Élément d'espace réservé.
Réessayez.
Élément utilisé pour déclarer des fragments de code HTML, qui ne sont pas affichés par défaut.
Bonne réponse !