Après avoir introduit les Web Components et sa première feature, les templates, je vais maintenant vous parler du Shadom DOM, qui va nous garantir l'encapsulation dans nos pages html.

Le shadow DOM... mais il est où ?

L'encapsulation d'aujourd'hui... en quelque sorte :(

<iframe>

Les iframes nous offrent un certain niveau d'encapsulation dans nos applications web mais restent trop contraignantes et problématiques pour être utilisées à grande échelle

L'encapsulation de demain

Le principe du Shadow DOM est de masquer en partie le DOM interne d'un composant. Afin de laisser la responsabilité de l'implémentation d'un composant à la personne qui le fournit.

En fouillant un peu dans le DOM, on se rend compte que certains éléments masquent déjà leur implémentation interne

Ainsi, un lecteur vidéo que l'on voit ainsi...

...et représenté ainsi dans le DOM...

<video controls src="/ma/super/video"></video>

...se révèle beaucoup plus complexe lorsque l'on active l'affichage du shadow DOM (Option disponible dans Chrome Developer Tools)

<video controls="" height="300" src="/ma/super/video">
  #shadow-root
    <div>
      <div>
        <div>
          <input type="button">
          <input type="range" precision="float" max="596.48">
          <div style="display: none;">0:00</div><div>9:56</div>
          <input type="button">
          <input type="range" precision="float" max="1" style="">
          <input type="button" style="display: none;">
          <input type="button" style="">
        </div>
      </div>
    </div>
</video>

Le Shadow DOM va donc nous permettre de mettre en place les mêmes mécanismes que ceux qui sont déjà utilisés par les navigateurs pour implémenter les composants natifs.

Comment créer du Shadow DOM

Pour utiliser le shadow DOM, il va vous falloir un élément "host", qui va accueillir notre shadow DOM, commme peut l'être la balise vidéo dans l'exemple précédent

En partant du DOM suivant...

<div id="host">
  <h1>Titre</h1>
  <h2>Sous-titre</h2>
  <div>contenu</div>
</div>

... on peut créer du shadow DOM via JavaScript

var host = document.querySelector('#host');
var shadow = host.createShadowRoot();
shadow.innerHTML = '<h2>Bouh ! Je suis le shadow DOMMMM !</h2>' +
                   '<div>et je viens de remplacer le contenu du noeud</div>';

Ce qui donne comme résultat :

<div id="host">
  #shadow-root
    <h2>Bouh ! Je suis le shadow DOMMMM !</h2>
    <div>et je viens de remplacer le contenu du noeud</div>
</div>

Problème ! On remarque ici que tout le contenu de notre noeud "host" a été supprimé et remplacé par le Shadow DOM. On voudrait bien garder notre contenu et l'insérer dans notre shadow DOM. Pour cela...

Shadow DOM insertion points

Les insertions points (ou points d'insertion en bon français) vont nous permette d'insérer le contenu de notre élément host au sein même de notre Shadow DOM.

Tout d'abord un exemple simple...

Pour définir des points d'insertion au sein de notre shadow DOM, nous allons utiliser la balise <content>

Imaginons que je souhaite utiliser le DOM suivant comme host...

<div id="host">
  <div>Hello World !</div>
</div>

pour ce shadow DOM...

<div>Je commence à apprendre les Web Components</div>
<content></content>
<div>J'ai fini d'apprendre les Web Components</div>

Une fois le Shadow DOM injecté, le résultat est donc...

<div id="host">
  #shadow-root
    <div>Je commence à apprendre les Web Components</div>
    <div>Hello World !</div>
    <div>J'ai fini d'apprendre les Web Components</div>
</div>

Utilisation des sélecteurs CSS

Voyons maintenant un exemple un peu plus complexe. Nous allons utiliser l'attribut select pour définir des sélecteurs CSS afin de spécifier quels éléments doivent être insérés et où.

Voici un exemple de shadow DOM dans lequel on définit plusieurs points d'insertion en utilisant des sélecteurs

<hgroup>
  <content select="h2"></content>
  <div>Bouh ! Je suis le shadow DOMMMM !</div>
  <content select="h1:first-child"></content>
</hgroup>
<content select="*"></content>

Si l'on reprend le DOM host original (avec Titre, Sous-titre et Contenu) et que l'on y injecte le shadow DOM, le résultat est le suivant

<div id="host">
  #shadow-root
    <hgroup>
      <h2>Sous-titre</h2>
      <div>Bouh ! Je suis le shadow DOMMMM !</div>
      <h1>Titre</h1>
    </hgroup>
    <div>contenu</div>
</div>

En plus de masquer l'implémentation interne des composants, le Shadow DOM nous permet donc de réordonner selon nos souhaits le DOM.

Encapsulation des styles

Maintenant que mon DOM est organisé comme je le souhaite, j'aimerais bien y ajouter un peu de couleur, de style...

Par défaut, le comportement est le suivant :

Dans l'exemple qui suit, le style color: red sera appliqué uniquement à la balise <h2> qui le suit. Les autres balises <h2> définies dans le reste de la page n'auront pas de visibilité sur ce style. À l'inverse, les styles définis globalement ne "rentreront" pas dans le shadow-root.

var host = document.querySelector('#host');
var shadow = host.createShadowRoot();
shadow.innerHTML = '<style>h2 { color: red; }</style>' + 
                   '<h2>Bouh ! Je suis le shadow DOMMMM !</h2>' +
                   '<div>et je viens de remplacer le contenu du noeud</div>';

Certaines propriétés sur l'élément shadow vont nous permettre de prendre le controle sur ces comportements

shadow.innerHTML = ...;
shadow.applyAuthorStyles = true;
shadow.resetStyleInheritance = true; 

Ajouter du style sur l'élément host

Le shadow DOM nous permet donc de scoper nos feuilles de style. Mais comment styler l'élement host ?

Tout simplement en utilisant le sélecteur @host dans notre feuille de style

<style>
  @host {
    /* Le style de l'élément host */
  }
</style>

La suite, la suite !

Après avoir introduit dans un premier temps les templates, nous venons de voir le Shadow DOM, deuxième fonctionnalité des Web Components.

Mais je vous l'accorde, ces deux éléments pris séparément ne semblent pas si révolutionnaires.

C'est pourquoi le prochain article va parler des... éléments customs. Comment utiliser les templates et le Shadow DOM pour enrichir la liste des éléments HTML avec ses propres éléments.


Les autres articles de la série sur les WebComponents :