Shadow DOM 101

Dominic Cooney
Dominic Cooney

Introduction

Web Components est un ensemble de normes de pointe qui:

  1. Permettre de créer des widgets
  2. ... qui peuvent être réutilisés de façon fiable
  3. ...et qui n'endommage pas les pages si la version suivante du composant modifie les détails de l'implémentation interne.

Cela signifie-t-il que vous devez décider quand utiliser HTML/JavaScript ? Quand utiliser les composants Web ? Non ! HTML et JavaScript peuvent rendre des éléments visuels interactifs. Les widgets sont des éléments visuels interactifs. Il il est logique d'exploiter vos compétences en HTML et JavaScript lorsque développer un widget. Les normes Web Components sont conçues pour aider de le faire.

Mais un problème fondamental fait que les widgets HTML et JavaScript difficiles à utiliser: dans un widget, l'arborescence DOM à partir du reste de la page. Ce manque d’encapsulation signifie que la feuille de style de votre document risque de s'appliquer par erreur dans le widget. votre code JavaScript pourrait modifier accidentellement des parties dans le widget. vos ID peuvent chevaucher ceux du widget. et ainsi de suite.

Les composants Web sont composés de trois parties:

  1. Modèles
  2. Shadow DOM
  3. Éléments personnalisés

Shadow DOM résout le problème d'encapsulation de l'arborescence DOM. La les quatre parties des composants Web sont conçues pour fonctionner ensemble, mais vous peuvent également choisir les parties des composants Web à utiliser. Ce ce tutoriel vous montre comment utiliser Shadow DOM.

Hello, Shadow World

Avec Shadow DOM, les éléments peuvent être associés à un nouveau type de nœud de l'IA générative. Ce nouveau type de nœud est appelé racine fantôme. Un élément auquel une racine fantôme est associée est appelé ombre hôte. Le contenu d'un hôte fantôme n'est pas affiché. le contenu de la racine fantôme est affichée à la place.

Par exemple, si vous avez un balisage de ce type:

<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>

alors au lieu de

<button id="ex1a">Hello, world!</button>
<script>
function remove(selector) {
  Array.prototype.forEach.call(
      document.querySelectorAll(selector),
      function (node) { node.parentNode.removeChild(node); });
}

if (!HTMLElement.prototype.createShadowRoot) {
  remove('#ex1a');
  document.write('<img src="SS1.png" alt="Screenshot of a button with \'Hello, world!\' on it.">');
}
</script>

l'apparence de votre page

<button id="ex1b">Hello, world!</button>
<script>
(function () {
  if (!HTMLElement.prototype.createShadowRoot) {
    remove('#ex1b');
    document.write('<img src="SS2.png" alt="Screenshot of a button with \'Hello, shadow world!\' in Japanese on it.">');
    return;
  }
  var host = document.querySelector('#ex1b');
  var root = host.createShadowRoot();
  root.textContent = 'こんにちは、影の世界!';
})();
</script>

De plus, si le code JavaScript de la page demande quel est le rôle textContent, il ne va pas "㟽の世界!", mais "Bonjour tout le monde !" car la sous-arborescence DOM sous la racine fantôme est encapsulé.

Séparation du contenu de la présentation

Voyons maintenant comment utiliser Shadow DOM pour séparer le contenu présentation. Imaginons que nous ayons cette balise de nom:

<style>
.ex2a.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.ex2a .boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.ex2a .name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="ex2a outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

Voici le balisage. C'est ce que vous écririez aujourd'hui. Non utilisez Shadow DOM:

<style>
.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

Étant donné que l'arborescence DOM n'a pas d'encapsulation, la structure entière du est exposé au document. Si d'autres éléments de la page utiliser accidentellement les mêmes noms de classe pour les styles ou les scripts, ce sera un mauvais moment.

On peut éviter de passer un mauvais moment.

Étape 1: Masquez les détails de la présentation

D'un point de vue sémantique, nous ne nous intéressons qu'aux éléments suivants:

  • Il s'agit d'un tag de nom.
  • Elle s'appelle "Bob".

Tout d'abord, nous écrivons un balisage plus proche de la véritable sémantique que nous souhaitons:

<div id="nameTag">Bob</div>

Ensuite, nous plaçons tous les styles et les div utilisés pour la présentation dans Un élément <template>:

<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<span class="unchanged"><style>
.outer {
  border: 2px solid brown;

  … same as above …

</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div></span>
</template>

À ce stade, "Bob" est le seul élément rendu. Parce que nous déplacé les éléments DOM de présentation à l'intérieur un élément <template>, elles ne sont pas affichées, mais ils sont accessibles depuis JavaScript. Nous le faisons maintenant pour remplissez la racine fantôme:

<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);

Maintenant que nous avons configuré une racine fantôme, le tag "name" s'affiche. à nouveau. Si vous faisiez un clic droit sur le tag associé à un nom et examinez vous voyez qu'il s'agit d'un balisage sémantique de bonne qualité:

<div id="nameTag">Bob</div>

Cela démontre qu'en utilisant Shadow DOM, nous avons masqué détails de présentation du tag de nom du document. La les détails de la présentation sont encapsulés dans le Shadow DOM.

Étape 2: Séparez le contenu de la présentation

Notre tag de nom masque désormais les détails de la présentation sur la page, mais il ne sépare pas réellement la présentation du contenu, car même si dont le contenu (le nom "Bob") figure sur la page, le nom qui s'affiche est celle que nous avons copiée dans la racine fantôme. Si nous voulons changer le sur le tag, il faudrait le faire à deux endroits, peut être désynchronisé.

Les éléments HTML sont des éléments de composition. Vous pouvez placer un bouton dans un tableau, par exemple. Nous avons besoin de la composition ici: le tag "name" doit être un d'un fond rouge, le texte "Hi!" le texte et le contenu qui figure sur l'étiquette de nom.

En tant qu'auteur du composant, vous définissez la façon dont la composition fonctionne widget à l'aide d'un nouvel élément appelé <content>. Ce crée un point d'insertion dans la présentation du widget, et Le point d'insertion sélectionne le contenu de l'hôte fantôme à présenter à ce stade.

Si nous remplaçons le balisage dans le Shadow DOM par le code suivant:

<span class="unchanged"><template id="nameTagTemplate">
<style>
  …
</style></span>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    <content></content>
  </div>
</div>
<span class="unchanged"></template></span>

Lorsque le tag de nom est affiché, le contenu de l'hôte fantôme est projetée à l'emplacement où l'élément <content> s'affiche.

Maintenant, la structure du document est plus simple car le nom est uniquement en un seul endroit : le document. Si votre page doit mettre à jour le il vous suffit d'écrire:

document.querySelector('#nameTag').textContent = 'Shellie';

et c'est tout. Le rendu du tag de nom est automatiquement mis à jour par le navigateur, car nous projectons le contenu du vous devez mettre en place le tag de nom avec <content>.

<div id="ex2b">

Nous avons réussi à séparer le contenu de la présentation. le contenu se trouve dans le document ; la présentation se trouve dans le Shadow DOM. Ils sont automatiquement synchronisés par le navigateur le moment venu. pour afficher un élément.

Étape 3: Bénéfices

En séparant le contenu et la présentation, nous pouvons simplifier qui manipule le contenu. Dans l'exemple des tags de nom, le code ne doit s'occuper qu'avec une structure simple contenant une <div> au lieu de plusieurs.

Maintenant, si nous modifions notre présentation, il n'est pas nécessaire de modifier les du code !

Par exemple, supposons que nous voulions localiser notre tag de nom. Il s'agit toujours d'un nom afin que le contenu sémantique du document ne change pas:

<div id="nameTag">Bob</div>

Le code de configuration de la racine fantôme reste le même. Ce qui est inclus dans modifications de la racine fantôme:

<template id="nameTagTemplate">
<style>
.outer {
  border: 2px solid pink;
  border-radius: 1em;
  background: url(sakura.jpg);
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
  font-family: sans-serif;
  font-weight: bold;
}
.name {
  font-size: 45pt;
  font-weight: normal;
  margin-top: 0.8em;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="name">
    <content></content>
  </div>
  と申します。
</div>
</template>

C'est une nette amélioration par rapport à la situation actuelle sur le Web, car le code de mise à jour du nom peut dépendre de la structure component simple et cohérent. Votre nom le code de mise à jour n'a pas besoin de connaître la structure utilisée pour le rendu. Si l'on considère ce qui est affiché, le nom en anglais (après "Hi! "My name is"), mais d'abord en japonais. (précédemment "地申います"). Cette distinction n'a aucun sens sémantique. du point de vue de la mise à jour du nom affiché, afin que le code de mise à jour du nom n’ait pas besoin de connaître ce détail.

Bonus: Projection avancée

Dans l'exemple ci-dessus, l'élément <content> sélectionne tout le contenu de l’hôte fantôme. En utilisant la propriété select, vous pouvez contrôler d'un élément de contenu. Vous pouvez également utiliser plusieurs contenus éléments.

Par exemple, si votre document contient les éléments suivants:

<div id="nameTag">
  <div class="first">Bob</div>
  <div>B. Love</div>
  <div class="email">bob@</div>
</div>

et une racine fantôme qui utilise des sélecteurs CSS pour sélectionner un contenu spécifique:

<div style="background: purple; padding: 1em;">
  <div style="color: red;">
    <content **select=".first"**></content>
  </div>
  <div style="color: yellow;">
    <content **select="div"**></content>
  </div>
  <div style="color: blue;">
    <content **select=".email">**</content>
  </div>
</div>

L'élément <div class="email"> correspond aux deux les éléments <content select="div"> et <content select=".email">. Combien de fois l'e-mail de Bob adresse apparaissent, et dans quelles couleurs ?

La réponse est que l'adresse e-mail de Bob apparaît une fois, et qu'elle est jaune.

En effet, comme le savent les pirates informatiques construire l'arborescence de ce qui est réellement affiché à l'écran revient à grande fête. L'élément "content" est l'invitation le contenu du document dans le rendu Shadow DOM en coulisses tiers. Ces invitations sont livrées dans l'ordre : qui obtient dépend de la personne à qui elle est adressée (c'est-à-dire, l'attribut select.) Contenu, une fois invité, accepte toujours l'invitation (qui ne le ferait pas ?!) et décline . Si une nouvelle invitation est envoyée à cette adresse, il n'y a personne à la maison, et il ne vient pas à votre fête.

Dans l'exemple ci-dessus, <div class="email"> correspond à le sélecteur div et .email du sélecteur, mais comme l'élément de contenu avec div est présenté plus tôt dans le document, <div class="email"> va au groupe jaune, et personne n'est disponible pour venir à la fête bleue. (Cela pourrait être pourquoi il est si bleu, même si la tristesse aime la compagnie, alors vous je ne le saurais jamais.)

Si un élément est invité à aucune session, il n'est pas n'est pas du tout affichée. C'est ce qui est arrivé au texte "Hello, world" dans au tout premier exemple. Cette approche est utile lorsque vous souhaitez un rendu radicalement différent: écrivez le modèle sémantique dans qui est accessible aux scripts de la page, mais masquez à des fins de rendu et de la connecter à un autre le rendu du modèle dans Shadow DOM à l'aide de JavaScript.

Par exemple, HTML dispose d'un bon sélecteur de date. Si vous écrivez <input type="date">, vous obtenez un calendrier pop-up soigné. Mais que se passe-t-il si vous permettre à l'utilisateur de choisir une plage de dates pour son dessert vacances sur une île (vous savez... avec des hamacs fabriqués en vignes rouges). Toi configurez votre document de cette manière:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

mais créer un Shadow DOM qui utilise une table pour créer un calendrier qui met en évidence la plage de dates, etc. Lorsque l'utilisateur clique sur jours du calendrier, le composant met à jour l'état dans les entrées startDate et endDate ; Lorsque l'utilisateur envoie le formulaire, les valeurs de ces éléments d'entrée sont envoyées.

Pourquoi ai-je inclus des étiquettes dans le document si elles ne seront pas s'affiche-t-il ? En effet, si un utilisateur affiche le formulaire dans un navigateur, qui n'est pas compatible avec Shadow DOM, mais le formulaire reste utilisable, joli. L'utilisateur voit quelque chose comme:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

Vous avez réussi le cours Shadow DOM 101

Vous connaissez maintenant les bases du Shadow DOM : vous venez de terminer avec les bases du Shadow DOM ! Vous pouvez pour exploiter tout le potentiel du Shadow DOM. Par exemple, vous pouvez utiliser plusieurs ombres un hôte fantôme, des ombres imbriquées pour l'encapsulation, ou l'architecture votre page à l'aide des vues basées sur des modèles (MDV) et du Shadow DOM. Et pour le Web Les composants sont bien plus que de simples Shadow DOM.

Nous les expliquerons dans des articles ultérieurs.