Qu’est-ce que le Shadow DOM ?

Ceci est une traduction de "What, exactly, is the Shadow DOM?", écrit par Ire Aderinokun et publié sur bitsofco.de. Par ici pour lire l'article original.

Il y a quelques semaines, j'ai écrit un article sur ce qu'est exactement le DOM. Pour récapituler, le Modèle d'Objet de Document est une représentation d'un document HTML. Il est utilisé par les navigateurs pour déterminer les éléments à afficher sur la page et par les programmes Javascript pour modifier le contenu, la structure ou le style de la page.

Par exemple, prenons le document HTML suivant:

<!doctype html>
<html lang="en">
 <head>
   <title>My first web page</title>
  </head>
 <body>
    <h1>Hello, world!</h1>
    <p>How are you?</p>
  </body>
</html>

Le document HTML ci-dessus entraînera l'arborescence DOM suivante.

Au cours des dernières années, vous avez peut-être entendu parler de termes tels que "Shadow DOM" ou "Virtual DOM". Ceux-ci, bien que liées au DOM d'origine, renvoient à des concepts très différents. Dans cet article, je vais expliquer en quoi consiste exactement le shadow DOM et en quoi il diffère du DOM d'origine. Dans un prochain article, je ferai la même chose pour le Virtual DOM.

Tout est global! 👍🏾 Quoi, Tout est global? 👎🏾

Tous les éléments et styles d'un document HTML, et donc du DOM, appartiennent à une seule et même étendue globale. La méthode document.querySelector() peut accéder à n'importe quel élément de la page, quelle que soit sa profondeur d'imbrication dans le document ou son emplacement. De même, les CSS appliquées au document peuvent sélectionner n’importe quel élément, quel que soit son emplacement.

Ce comportement peut être très utile lorsque nous souhaitons appliquer des styles à l’ensemble du document. Il est extrêmement utile de pouvoir sélectionner chaque élément d’une page et de définir, par exemple le box-sizing, le tout sur une seule ligne.

* { box-sizing: border-box }

D’autre part, il arrive parfois qu’un élément nécessite une encapsulation complète et que nous ne voulons pas qu’il soit affecté par les styles, même globaux. Les widgets tiers, tels que le bouton "Follow" de Twitter, en sont un bon exemple. Voici un exemple de ce à quoi ressemble ce widget:

Follow @ireaderinokun

C'est la seule façon pour Twitter de s'assurer que le style souhaité de son widget ne sera pas affecté par les CSS du document d'hébergement. Bien qu’il existe des moyens d’utiliser la cascade pour essayer d’obtenir le même résultat, aucune autre méthode ne donnera la même garantie qu’un <iframe>. Le shadow DOM a été créé pour permettre l’encapsulation et la composantisation de manière native sur la plate-forme Web sans avoir à recourir à des outils tels que <iframe>, qui n’étaient pas vraiment conçus à cette fin.

Un DOM dans un DOM

Vous pouvez considérer le shadow DOM comme un "DOM dans un DOM". Il est son propre arbre DOM isolé avec ses propres éléments et styles, complètement isolé du DOM d'origine.

Bien que récemment défini pour une utilisation par les auteurs web, le shadow DOM est utilisé depuis des années par les agents utilisateurs pour créer et mettre en forme des composants complexes tels que des éléments de formulaire. Prenons l’élément input range, par exemple. Pour en créer un sur la page, il suffit d'ajouter l'élément suivant:

<input type="range">

Cet élément entraîne le composant suivant:

Si nous creusons plus profondément, nous verrons que cet élément <input> est en réalité composé de plusieurs éléments <div> plus petits, contrôlant la piste et le curseur lui-même.

Ceci est réalisé en utilisant le shadow DOM. L'élément exposé au document HTML hôte est simplement <input>, mais sous ce dernier se trouvent des éléments et des styles liés au composant qui ne font pas partie de la portée globale du DOM.

Comment fonctionne le Shadow DOM

Pour illustrer le fonctionnement du DOM fantôme, recréons le bouton "Follow" de Twitter en utilisant le shadow DOM fantôme au lieu d’une <iframe>.

Tout d’abord, nous commençons par le shadow-host. Il s'agit de l'élément HTML normal du DOM d'origine auquel nous souhaitons associer le nouveau shadow DOM. Pour un composant tel que le bouton Follow, il pourrait également contenir l'élément de secours que nous voudrions afficher si Javascript n'était pas activé sur la page ou si le shadow DOM n'était pas pris en charge.

<span class="shadow-host">
  <a href="https://twitter.com/ireaderinokun">
     Follow @ireaderinokun
  </a>
</span>

Notez que nous n'avons pas simplement utilisé l'élément <a> comme shadow host, car certains éléments, principalement des éléments interactifs, ne peuvent pas être des "hôtes fictifs".

Pour attacher un shadow DOM à notre hôte, nous utilisons la méthode attachShadow().

const shadowEl = document.querySelector(".shadow-host");
const shadow = shadowEl.attachShadow({mode: 'open'});

Cela créera un shadow root vide en tant qu'enfant de notre shadow host. Le shadow root est le début d'un nouveau shadow DOM, de la même manière que l'élément <html> est le début du DOM d'origine. Nous pouvons voir notre shadow root dans l'inspecteur devtools via le #shadow-root.

Bien que les enfants HTML normaux soient visibles dans l'inspecteur, ils ne sont plus visibles sur la page lorsque le shadow root prend le relais.

Ensuite, nous voulons créer le contenu pour former notre nouvel arbre shadow tree. Le shadow tree est semblable à un arbre DOM tree, mais pour un shadow DOM au lieu d'un DOM normal. Pour créer notre bouton Follow, tout ce dont nous avons besoin est un nouvel élément <a>, qui sera presque exactement le même que le lien de secours que nous avons déjà, mais avec une icône.

const link = document.createElement("a");
link.href = shadowEl.querySelector("a").href;
link.innerHTML = `
    <span aria-label="Twitter icon"></span> 
    ${shadowEl.querySelector("a").textContent}
`;

Nous ajoutons ce nouvel élément à notre shadow DOM de la même manière que nous ajoutons n'importe quel élément en tant qu'enfant avec un autre, avec la méthode appendChild().

shadow.appendChild(link);

À ce stade, voici à quoi ressemble notre élément:

Enfin, nous pouvons ajouter des styles en créant un élément <style> et en l'ajoutant également à la shadow root.

const styles = document.createElement("style");
styles.textContent = `
a, span {
  vertical-align: top;
  display: inline-block;
  box-sizing: border-box;
}

a {
    height: 20px;
    padding: 1px 8px 1px 6px;
    background-color: #1b95e0;
    color: #fff;
    border-radius: 3px;
    font-weight: 500;
    font-size: 11px;
    font-family:'Helvetica Neue', Arial, sans-serif;
    line-height: 18px;
    text-decoration: none;   
}

a:hover {  background-color: #0c7abf; }

span {
    position: relative;
    top: 2px;
    width: 14px;
    height: 14px;
    margin-right: 3px;
    background: transparent 0 0 no-repeat;
    background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2072%2072%22%3E%3Cpath%20fill%3D%22none%22%20d%3D%22M0%200h72v72H0z%22%2F%3E%3Cpath%20class%3D%22icon%22%20fill%3D%22%23fff%22%20d%3D%22M68.812%2015.14c-2.348%201.04-4.87%201.744-7.52%202.06%202.704-1.62%204.78-4.186%205.757-7.243-2.53%201.5-5.33%202.592-8.314%203.176C56.35%2010.59%2052.948%209%2049.182%209c-7.23%200-13.092%205.86-13.092%2013.093%200%201.026.118%202.02.338%202.98C25.543%2024.527%2015.9%2019.318%209.44%2011.396c-1.125%201.936-1.77%204.184-1.77%206.58%200%204.543%202.312%208.552%205.824%2010.9-2.146-.07-4.165-.658-5.93-1.64-.002.056-.002.11-.002.163%200%206.345%204.513%2011.638%2010.504%2012.84-1.1.298-2.256.457-3.45.457-.845%200-1.666-.078-2.464-.23%201.667%205.2%206.5%208.985%2012.23%209.09-4.482%203.51-10.13%205.605-16.26%205.605-1.055%200-2.096-.06-3.122-.184%205.794%203.717%2012.676%205.882%2020.067%205.882%2024.083%200%2037.25-19.95%2037.25-37.25%200-.565-.013-1.133-.038-1.693%202.558-1.847%204.778-4.15%206.532-6.774z%22%2F%3E%3C%2Fsvg%3E);
}
`;

shadow.appendChild(styles);

Voici notre dernier élément:

Le DOM vs le Shadow DOM

À certains égards, le shadow DOM est une version "allégée" du DOM. Tout comme le DOM, il s'agit d'une représentation des éléments HTML, utilisée pour déterminer les éléments à rendre sur la page et permettant la modification des éléments. Mais contrairement au DOM, le shadow DOM n’est pas basé sur un document complet et autonome. Comme son nom l'indique, un shadow DOM est toujours attaché à un élément d'un DOM normal. Sans le DOM, le shadow DOM n'existe pas.