Movatterモバイル変換


[0]ホーム

URL:


  1. Web
  2. Les API Web
  3. Web Components (composants web)
  4. Utiliser le DOM d'ombre

Cette page a été traduite à partir de l'anglais par la communauté.Vous pouvez contribuer en rejoignant la communauté francophone sur MDN Web Docs.

View in EnglishAlways switch to English

Utiliser le DOM d'ombre

Un aspect important des éléments personnalisés est l'encapsulation, car un élément personnalisé, par définition, est une fonctionnalité réutilisable : il peut être inséré dans n'importe quelle page web et on s'attend à ce qu'il fonctionne. Il est donc important que le code exécuté dans la page ne puisse pas accidentellement casser un élément personnalisé en modifiant son implémentation interne. Le DOM d'ombre (shadow DOM en anglais) permet d'associer un arbre DOM à un élément, et d'avoir l'intérieur de cet arbre inaccessible au JavaScript et au CSS exécutés dans la page.

Cet article présente les bases de l'utilisation du DOM d'ombre.

Vue de haut niveau

Cet article suppose que vous êtes déjà familier avec le concept deDOM (Document Object Model) — une structure arborescente de nœuds connectés représentant les différents éléments et chaînes de caractères apparaissant dans un document balisé (généralement un document HTML dans le cas de documents web). Par exemple, considérez le fragment HTML suivant :

html
<html lang="fr">  <head>    <meta charset="utf-8" />    <title>Simple exemple de DOM</title>  </head>  <body>    <section>      <img        src="dinosaur.png"        alt="Un tyrannosaurus Rex rouge&nbsp;: un dinosaure bipède se tenant debout comme un humain, avec de petits bras et une large gueule à nombreuses dents tranchantes." />      <p>        Nous ajouterons ici un lien vers la        <a href="https://www.mozilla.org/">page d'accueil de Mozilla</a>      </p>    </section>  </body></html>

Ce fragment produit la structure DOM suivante :

- HTML    - HEAD        - META charset="utf-8"        - TITLE            - #text: Simple exemple de DOM    - BODY        - SECTION            - IMG src="dinosaur.png" alt="Un tyrannosaurus Rex rouge : un dinosaure bipède se tenant debout comme un humain, avec de petits bras et une large gueule à nombreuses dents tranchantes."            - P                - #text: Ici, nous ajouterons un lien vers la                - A href="https://www.mozilla.org/"                    - #text: page d'accueil de Mozilla

Le DOM d'ombre permet d'associer des arbres DOM cachés à des éléments dans l'arbre DOM principal : cet arbre DOM d'ombre commence par une racine d'ombre, sous laquelle vous pouvez attacher n'importe quel élément, comme dans le DOM classique.

Version SVG du schéma montrant l'interaction entre le document, la racine d'ombre et l'hôte d'ombre.

Il existe quelques termes spécifiques au DOM d'ombre à connaître :

  • Hôte d'ombre : le nœud DOM classique auquel le DOM d'ombre est attaché.
  • Arbre d'ombre : l'arbre DOM à l'intérieur du DOM d'ombre.
  • Limite d'ombre : l'endroit où le DOM d'ombre se termine et où le DOM classique commence.
  • Racine d'ombre : le nœud racine de l'arbre d'ombre.

Vous pouvez manipuler les nœuds dans le DOM d'ombre exactement comme les nœuds classiques : par exemple, en ajoutant des enfants ou en définissant des attributs, en mettant en forme des nœuds individuels avec element.style.foo, ou en ajoutant du style à tout l'arbre DOM d'ombre à l'intérieur d'un élément<style>. La différence est qu'aucun code à l'intérieur d'un DOM d'ombre ne peut affecter quoi que ce soit à l'extérieur, ce qui permet une encapsulation pratique.

Avant que le DOM d'ombre ne soit mis à disposition des développeur·euse·s web, les navigateurs l'utilisaient déjà pour encapsuler la structure interne d'un élément. Pensez par exemple à un élément<video>, avec les contrôles par défaut du navigateur affichés. Tout ce que vous voyez dans le DOM est l'élément<video>, mais il contient une série de boutons et d'autres contrôles à l'intérieur de son DOM d'ombre. La spécification du DOM d'ombre vous permet de manipuler le DOM d'ombre de vos propres éléments personnalisés.

Héritage des attributs

L'arbre d'ombre et les éléments<slot> héritent des attributsdir etlang de leur hôte d'ombre.

Création d'un DOM d'ombre

De manière impérative avec JavaScript

La page suivante contient deux éléments, un élément HTML<div> avec unid de"host", et un élément HTML<span> contenant du texte :

html
<div></div><span>Je ne suis pas dans le DOM d'ombre</span>

Nous allons utiliser l'élément"host" comme hôte d'ombre. Nous appelonsattachShadow() sur l'hôte pour créer le DOM d'ombre, puis nous pouvons ajouter des nœuds au DOM d'ombre comme nous le ferions dans le DOM principal. Dans cet exemple, nous ajoutons un seul élément<span> :

js
const host = document.querySelector("#host");const shadow = host.attachShadow({ mode: "open" });const span = document.createElement("span");span.textContent = "Je suis dans le DOM d'ombre";shadow.appendChild(span);

Le résultat ressemble à ceci :

De manière déclarative avec HTML

Créer un DOM d'ombre via l'API JavaScript peut être une bonne option pour les applications rendues côté client. Pour d'autres applications, une interface utilisateur rendue côté serveur peut offrir de meilleures performances et donc une meilleure expérience utilisateur. Dans ces cas, vous pouvez utiliser l'élément<template> pour définir le DOM d'ombre de manière déclarative. La clé de ce comportement est l'attributénuméréshadowrootmode, qui peut être défini suropen ouclosed, les mêmes valeurs que l'optionmode de la méthodeattachShadow().

html
<div>  <template shadowrootmode="open">    <span>Je suis dans le DOM d'ombre</span>  </template></div>

Note :Par défaut, le contenu de<template> n'est pas affiché. Dans ce cas, puisqueshadowrootmode="open" a été inclus, la racine d'ombre est rendue. Dans les navigateurs compatibles, le contenu visible à l'intérieur de cette racine d'ombre est affiché.

Après que le navigateur a analysé le HTML, il remplace l'élément<template> par son contenu enveloppé dans uneracine d'ombre qui est attachée à l'élément parent, le<div> dans notre exemple. L'arbre DOM résultant ressemble à ceci (il n'y a pas d'élément<template> dans l'arbre DOM) :

- DIV  - #shadow-root    - SPAN      - #text: Je suis dans le DOM d'ombre

Notez qu'en plus deshadowrootmode, vous pouvez utiliser des attributs de<template> tels queshadowrootclonable etshadowrootdelegatesfocus pour préciser d'autres propriétés de la racine d'ombre générée.

Encapsulation depuis JavaScript

Cela peut paraître peu spectaculaire pour l'instant. Voyons ce qui se passe si le code s'exécutant dans la page tente d'accéder aux éléments du DOM d'ombre.

Cette page est identique à la précédente, sauf que nous avons ajouté deux éléments<button>.

html
<div></div><span>Je ne suis pas dans le DOM d'ombre</span><br /><button type="button">Mettre en majuscules les éléments span</button><button type="button">Recharger</button>

Cliquer sur le bouton « Mettre en majuscules les éléments span » recherche tous les éléments<span> de la page et transforme leur texte en majuscules.Cliquer sur « Recharger » recharge simplement la page, pour réessayer.

js
const host = document.querySelector("#host");const shadow = host.attachShadow({ mode: "open" });const span = document.createElement("span");span.textContent = "Je suis dans le DOM d'ombre";shadow.appendChild(span);const upper = document.querySelector("button#upper");upper.addEventListener("click", () => {  const spans = Array.from(document.querySelectorAll("span"));  for (const span of spans) {    span.textContent = span.textContent.toUpperCase();  }});const reload = document.querySelector("#reload");reload.addEventListener("click", () => document.location.reload());

Si vous cliquez sur « Mettre en majuscules les éléments span », vous verrez queDocument.querySelectorAll() ne trouve pas les éléments dans notre DOM d'ombre : ils sont effectivement invisibles pour le JavaScript de la page.

Element.shadowRoot et l'option « mode »

Dans l'exemple ci‑dessous, nous passons l'argument{ mode: "open" } àattachShadow(). Lorsquemode est défini sur"open", le JavaScript de la page peut accéder à l'intérieur de votre DOM d'ombre via la propriétéshadowRoot de l'hôte d'ombre.

Dans cet exemple, comme précédemment, le HTML contient l'hôte d'ombre, un<span> dans l'arbre DOM principal et deux boutons :

html
<div></div><span>Je ne suis pas dans le DOM d'ombre</span><br /><button type="button">  Mettre en majuscules les éléments span du DOM d'ombre</button><button type="button">Recharger</button>

Cette fois, le bouton « Mettre en majuscules » utiliseshadowRoot pour trouver les<span> dans le DOM :

js
const host = document.querySelector("#host");const shadow = host.attachShadow({ mode: "open" });const span = document.createElement("span");span.textContent = "Je suis dans le DOM d'ombre";shadow.appendChild(span);const upper = document.querySelector("button#upper");upper.addEventListener("click", () => {  const spans = Array.from(host.shadowRoot.querySelectorAll("span"));  for (const span of spans) {    span.textContent = span.textContent.toUpperCase();  }});const reload = document.querySelector("#reload");reload.addEventListener("click", () => document.location.reload());

Cette fois, le JavaScript de la page peut accéder aux éléments internes du DOM d'ombre :

L'argument{mode: "open"} offre à la page un moyen de rompre l'encapsulation de votre DOM d'ombre. Si vous ne souhaitez pas donner cette possibilité, passez{mode: "closed"} etshadowRoot renverranull.

Cependant, cela ne doit pas être considéré comme un mécanisme de sécurité solide, car il existe des moyens de le contourner, par exemple via des extensions de navigateur s'exécutant dans la page. Il s'agit plutôt d'une indication que la page ne doit pas accéder à l'implémentation interne de votre arbre d'ombre.

Encapsulation depuis le CSS

Dans cette version de la page, le HTML est identique à l'original :

html
<div></div><span>Je ne suis pas dans le DOM d'ombre</span>

En JavaScript, nous créons le DOM d'ombre :

js
const host = document.querySelector("#host");const shadow = host.attachShadow({ mode: "open" });const span = document.createElement("span");span.textContent = "Je suis dans le DOM d'ombre";shadow.appendChild(span);

Cette fois, nous avons du CSS ciblant les éléments<span> de la page :

css
span {  color: blue;  border: 1px solid black;}

Le CSS de la page n'affecte pas les nœuds situés à l'intérieur du DOM d'ombre :

Appliquer des styles dans le DOM d'ombre

Dans cette section, nous examinons deux façons d'appliquer des styles dans un arbre de DOM d'ombre :

Dans les deux cas, les styles définis dans l'arbre du DOM d'ombre sont limités à cet arbre : tout comme les styles de la page n'affectent pas les éléments du DOM d'ombre, les styles du DOM d'ombre n'affectent pas les éléments du reste de la page.

Feuilles de style constructibles

Pour mettre en forme des éléments dans le DOM d'ombre à l'aide de feuilles de style constructibles, nous pouvons :

  1. Créer un objetCSSStyleSheet vide
  2. Définir son contenu en utilisantCSSStyleSheet.replace() ouCSSStyleSheet.replaceSync()
  3. L'ajouter à la racine d'ombre en l'assignant àShadowRoot.adoptedStyleSheets

Les règles définies dans laCSSStyleSheet seront limitées à l'arbre du DOM d'ombre, ainsi qu'à tout autre arbre DOM auquel nous l'avons assignée.

Ici, encore une fois, le HTML contient notre hôte et un<span> :

html
<div></div><span>Je ne suis pas dans le DOM d'ombre</span>

Cette fois, nous créons le DOM d'ombre et lui assignons un objetCSSStyleSheet :

js
const sheet = new CSSStyleSheet();sheet.replaceSync("span { color: red; border: 2px dotted black;}");const host = document.querySelector("#host");const shadow = host.attachShadow({ mode: "open" });shadow.adoptedStyleSheets = [sheet];const span = document.createElement("span");span.textContent = "Je suis dans le DOM d'ombre";shadow.appendChild(span);

Les styles définis dans l'arbre du DOM d'ombre ne s'appliquent pas au reste de la page :

Ajouter des éléments<style> dans des déclarations<template>

Une alternative à la construction d'objetsCSSStyleSheet consiste à inclure un élément<style> à l'intérieur de l'élément<template> utilisé pour définir un composant Web.

Dans ce cas, le HTML inclut la déclaration<template> :

html
<template>  <style>    span {      color: red;      border: 2px dotted black;    }  </style>  <span>Je suis dans le DOM d'ombre</span></template><div></div><span>Je ne suis pas dans le DOM d'ombre</span>

En JavaScript, nous créons le DOM d'ombre et ajoutons le contenu du<template> :

js
const host = document.querySelector("#host");const shadow = host.attachShadow({ mode: "open" });const template = document.getElementById("my-element");shadow.appendChild(template.content);

Encore une fois, les styles définis dans le<template> ne s'appliquent qu'à l'intérieur de l'arbre du DOM d'ombre, et pas au reste de la page :

Choisir entre les options programmatiques et déclaratives

Le choix entre ces options dépend de votre application et de vos préférences personnelles.

Créer un objetCSSStyleSheet et l'assigner à la racine d'ombre viaadoptedStyleSheets permet de créer une seule feuille de style et de la partager entre plusieurs arbres DOM. Par exemple, une bibliothèque de composants peut créer une seule feuille de style et la partager entre tous les éléments personnalisés de cette bibliothèque. Le navigateur analysera cette feuille une seule fois. De plus, vous pouvez modifier dynamiquement la feuille de style et propager ces changements à tous les composants qui l'utilisent.

L'approche consistant à attacher un élément<style> est idéale si vous souhaitez être déclaratif, que vous avez peu de styles et que vous n'avez pas besoin de partager les styles entre différents composants.

DOM d'ombre et éléments personnalisés

Sans l'encapsulation fournie par le DOM d'ombre,les éléments personnalisés seraient extrêmement fragiles. Il serait trop facile pour une page de casser accidentellement le comportement ou la mise en page d'un élément personnalisé en exécutant du JavaScript ou du CSS sur la page. En tant que développeur·euse d'éléments personnalisés, vous ne sauriez jamais si les sélecteurs applicables à l'intérieur de votre élément entraient en conflit avec ceux d'une page qui utilise votre élément personnalisé.

Les éléments personnalisés sont implémentés comme une classe qui étend soit la baseHTMLElement, soit un élément HTML natif tel queHTMLParagraphElement. En général, l'élément personnalisé lui-même est un hôte d'ombre, et l'élément crée plusieurs éléments sous cette racine pour fournir l'implémentation interne de l'élément.

L'exemple ci-dessous crée un élément personnalisé<filled-circle> qui affiche simplement un cercle rempli d'une couleur unie.

js
class FilledCircle extends HTMLElement {  constructor() {    super();  }  connectedCallback() {    // Créer une racine d'ombre    // L'élément personnalisé lui-même est l'hôte d'ombre    const shadow = this.attachShadow({ mode: "open" });    // créer l'implémentation interne    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");    const circle = document.createElementNS(      "http://www.w3.org/2000/svg",      "circle",    );    circle.setAttribute("cx", "50");    circle.setAttribute("cy", "50");    circle.setAttribute("r", "50");    circle.setAttribute("fill", this.getAttribute("color"));    svg.appendChild(circle);    shadow.appendChild(svg);  }}customElements.define("filled-circle", FilledCircle);
html
<filled-circle color="blue"></filled-circle>

Pour d'autres exemples illustrant différents aspects de l'implémentation d'éléments personnalisés, consultez notreguide sur les éléments personnalisés.

Voir aussi

Help improve MDN

Learn how to contribute

Cette page a été modifiée le par lescontributeurs du MDN.


[8]ページ先頭

©2009-2025 Movatter.jp