Aller au contenu

Shadow DOM JavaScript

Le Shadow DOM est une fonctionnalité clé des Web Components, permettant aux développeurs de créer des arbres DOM encapsulés et des périmètres de style. Ce guide est conçu pour fournir une compréhension approfondie du Shadow DOM, ainsi que des exemples de code pratiques pour démontrer son utilisation.

Qu’est-ce que le Shadow DOM ?

Le Shadow DOM permet aux développeurs d’encapsuler une partie du DOM et ses styles, en l’isolant du reste du document. Cela garantit que les styles et les scripts à l’intérieur du Shadow DOM n’entrent pas en conflit avec ceux du document principal.


html
<head>
  <style>
    .shadow-box {
      padding: 10px;
      border: 1px solid #000;
      background-color: lightcoral;
      color: white;
    }
  </style>
</head>
<body>
  <div class="shadow-box">This is styled by the main document</div>
  <div id="host"></div>
  <script>
    // Create a shadow root
    const hostElement = document.getElementById('host');
    const shadowRoot = hostElement.attachShadow({ mode: 'open' });

    // Attach shadow DOM content
    shadowRoot.innerHTML = `
      <style>
        .shadow-box {
          padding: 10px;
          border: 1px solid #000;
          background-color: lightblue;
          color: black;
        }
      </style>
      <div class="shadow-box">Hello, Shadow DOM!</div>
    `;
  </script>
</body>

Dans cet exemple, il y a deux éléments avec le même nom de classe shadow-box. Le premier élément est stylé par le CSS du document principal, tandis que le second élément est stylé par le CSS du Shadow DOM. Comme vous pouvez le voir, les styles définis dans le Shadow DOM n’affectent pas les éléments du document principal, et vice versa. Cela démontre l’encapsulation fournie par le Shadow DOM, vous permettant de créer des composants isolés et réutilisables sans vous soucier des conflits de styles.

Créer un Shadow Root

Pour créer un shadow root, utilisez la méthode attachShadow sur un élément. Le shadow root peut être soit open, soit closed. Un shadow root open est accessible depuis du JavaScript externe à l’arbre shadow, tandis qu’un shadow root closed ne l’est pas.

Shadow Root ouvert

Un shadow root ouvert permet l’accès et la manipulation depuis du JavaScript externe. Dans l’exemple ci-dessous, nous manipulons le contenu textuel à l’intérieur du shadow root après sa création.


html
<body>
  <div id="open-shadow-host"></div>
  <button id="open-shadow-btn">Change Shadow Content</button>

  <script>
    const openShadowHost = document.getElementById('open-shadow-host');
    const openShadowRoot = openShadowHost.attachShadow({ mode: 'open' });

    openShadowRoot.innerHTML = `
      <style>
        .shadow-content {
          color: blue;
          padding: 10px;
          border: 1px solid black;
        }
      </style>
      <div class="shadow-content">This is an open shadow root</div>
    `;

    document.getElementById('open-shadow-btn').addEventListener('click', () => {
      openShadowRoot.querySelector('.shadow-content').textContent = 'Open Shadow Root content updated!';
    });
  </script>
</body>

Dans cet exemple, un bouton est fourni pour modifier le contenu du shadow DOM. Comme le shadow root est ouvert, nous pouvons accéder à son contenu et le manipuler depuis le document principal.

Shadow Root fermé

Un shadow root fermé restreint l’accès depuis des scripts externes, offrant une meilleure encapsulation. Dans l’exemple ci-dessous, nous essayons de manipuler le contenu textuel à l’intérieur du shadow root après sa création, mais ce n’est pas possible puisqu’il est closed.


html
<body>
  <div id="closed-shadow-host"></div>
  <button id="closed-shadow-btn">Try to Change Shadow Content</button>

  <script>
    const closedShadowHost = document.getElementById('closed-shadow-host');
    const closedShadowRoot = closedShadowHost.attachShadow({ mode: 'closed' });

    closedShadowRoot.innerHTML = `
      <style>
        .shadow-content {
          color: red;
          padding: 10px;
          border: 1px solid black;
        }
      </style>
      <div class="shadow-content">This is a closed shadow root</div>
    `;

    // closedShadowHost.shadowRoot is null for closed roots, so this throws a TypeError
    document.getElementById('closed-shadow-btn').addEventListener('click', () => {
      try {
        closedShadowHost.shadowRoot.querySelector('.shadow-content').textContent = 'Attempted to update closed shadow root!';
      } catch (e) {
        alert('Cannot access shadow root content from outside!');
      }
    });
  </script>
</body>

Dans cet exemple, une tentative d’accès et de manipulation du contenu du shadow DOM échoue parce que le shadow root est fermé. Notez que pour les shadow roots fermés, la propriété shadowRoot sur l’élément hôte est null, ce qui provoque une erreur TypeError lors de l’appel null.querySelector. Cela montre comment les shadow roots fermés offrent une meilleure encapsulation en restreignant l’accès. Remarque : les shadow roots fermés sont pris en charge dans tous les navigateurs modernes, mais ils masquent intentionnellement la racine au JavaScript externe afin de renforcer l’encapsulation. Utilisez-les lorsque vous souhaitez empêcher un accès externe accidentel.

Stylisation dans le Shadow DOM

WARNING

Lors de l’implémentation du Shadow DOM en JavaScript, assurez-vous d’une encapsulation correcte afin d’éviter des conflits de style ou de script involontaires.

Les styles définis à l’intérieur d’un shadow root n’affectent pas les éléments situés à l’extérieur, et vice versa. Cette encapsulation est utile pour créer des composants réutilisables.


html
<head>
  <style>
    .styled-box {
      color: red;
      background-color: yellow;
      padding: 10px;
      border: 1px solid green;
    }
  </style>
</head>
<body>
  <div class="styled-box">This is styled by the main document</div>
  <div id="styled-host"></div>

  <script>
    const styledHost = document.getElementById('styled-host');
    const shadowRoot = styledHost.attachShadow({ mode: 'open' });

    shadowRoot.innerHTML = `
      <style>
        .styled-box {
          color: white;
          background-color: black;
          padding: 10px;
          border-radius: 5px;
        }
      </style>
      <div class="styled-box">Styled by Shadow DOM</div>
    `;
  </script>
</body>

Dans cet exemple, il y a deux éléments avec le nom de classe styled-box. Le premier élément est stylé par le CSS du document principal, tandis que le second élément est stylé par le CSS du Shadow DOM. Les styles définis dans le Shadow DOM n’affectent pas les éléments du document principal, et les styles définis dans le document principal n’affectent pas les éléments du Shadow DOM. Cela montre comment le Shadow DOM encapsule les styles, garantissant l’absence de conflits entre les styles du composant et les styles globaux.

Pour styliser des éléments à l’intérieur du Shadow DOM depuis l’extérieur, utilisez les Shadow Parts CSS (::part()) et les pseudo-éléments distribués (::slotted()). Ceux-ci permettent au CSS externe de cibler des parties internes spécifiques ou du contenu projeté sans rompre l’encapsulation.

Slotting : contenu du Light DOM dans le Shadow DOM

Les slots permettent aux développeurs de transmettre du contenu du light DOM (DOM classique) dans un shadow DOM, rendant le shadow DOM plus flexible et réutilisable.


html
<div id="slot-host">
  <span slot="title">Shadow DOM Slot Example</span>
</div>

<script>
  const slotHost = document.getElementById('slot-host');
  const shadowRoot = slotHost.attachShadow({ mode: 'open' });

  shadowRoot.innerHTML = `
    <style>
      .container {
        border: 1px solid #ccc;
        padding: 10px;
      }
    </style>
    <div class="container">
      <h1><slot name="title"></slot></h1>
      <p>This is a Shadow DOM component with a slot for the title.</p>
    </div>
  `;
</script>

Dans cet exemple, l’élément <slot> est utilisé pour transmettre du contenu du light DOM vers le shadow DOM. L’attribut slot sur l’élément span correspond à l’attribut name de l’élément slot dans le shadow DOM, ce qui permet de projeter le contenu du span dans le shadow DOM.

Interaction JavaScript avec le Shadow DOM

Interagir avec le Shadow DOM via JavaScript nécessite de comprendre les limites d’encapsulation. La manipulation directe à l’intérieur du shadow root est simple, mais l’interaction externe demande une gestion attentive.

Accéder aux éléments du Shadow DOM

Pour accéder aux éléments à l’intérieur d’un shadow DOM, utilisez la propriété shadowRoot.


html
<div id="interactive-host"></div>

<script>
  const interactiveHost = document.getElementById('interactive-host');
  const shadowRoot = interactiveHost.attachShadow({ mode: 'open' });

  shadowRoot.innerHTML = `
    <button id="shadow-btn">Click me</button>
  `;

  const shadowButton = shadowRoot.querySelector('#shadow-btn');
  shadowButton.addEventListener('click', () => {
    alert('Button inside Shadow DOM clicked!');
  });
</script>

Dans cet exemple, nous accédons au bouton à l’intérieur du shadow DOM en utilisant querySelector sur le shadow root. Comme le shadow root est ouvert, nous pouvons attacher des écouteurs d’événements et manipuler directement les éléments depuis le document principal.

Exemples pratiques de Shadow DOM

Créer un Web Component réutilisable

Créer un web component réutilisable à l’aide du Shadow DOM consiste à définir un élément personnalisé et à lui attacher un shadow root.


html
<body>
  <custom-card title="Hello World"></custom-card>

  <script>
    class CustomCard extends HTMLElement {
      constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.innerHTML = `
          <style>
            .card {
              padding: 10px;
              border: 1px solid #ddd;
              border-radius: 5px;
              box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            }
            .card-title {
              font-size: 1.2em;
              margin-bottom: 5px;
            }
          </style>
          <div class="card">
            <div class="card-title">${this.getAttribute('title')}</div>
            <div class="card-content"><slot></slot></div>
          </div>
        `;
      }
    }

    customElements.define('custom-card', CustomCard);
  </script>
</body>

Dans cet exemple, un élément personnalisé &lt;custom-card&gt; est créé avec un shadow DOM. Le shadow DOM encapsule les styles et la structure du composant, le rendant réutilisable sans se soucier des conflits de styles avec le document principal.

Intégration avec des frameworks

Le Shadow DOM peut être utilisé de manière fluide avec des frameworks JavaScript modernes comme React, Angular et Vue.

Exemple React

Dans React, vous pouvez attacher un shadow DOM à un élément conteneur comme suit :


html
<body>
  <div id="root"></div>
  <!-- React and ReactDOM CDN links -->
  <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script type="text/babel">
    const { useRef, useLayoutEffect } = React;

    const CustomCard = ({ title, content }) => {
      const cardRef = useRef(null);

      useLayoutEffect(() => {
        if (cardRef.current) {
          const shadowRoot = cardRef.current.attachShadow({ mode: 'open' });

          shadowRoot.innerHTML = `
            <style>
              .card {
                padding: 10px;
                border: 1px solid #ddd;
                border-radius: 5px;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
              }
              .card-title {
                font-size: 1.2em;
                margin-bottom: 5px;
              }
            </style>
            <div class="card">
              <div class="card-title">${title}</div>
              <div class="card-content">${content}</div>
            </div>
          `;
        }
      }, [title, content]);

      return <div ref={cardRef}></div>;
    };

    const App = () => (
      <CustomCard title="Hello World" content="This is content inside the shadow DOM.">
      </CustomCard>
    );

    const rootElement = document.getElementById('root');
    const root = ReactDOM.createRoot(rootElement);
    root.render(<App />);
  </script>
</body>

Dans cet exemple, un composant React CustomCard est créé et attache un shadow DOM à une simple div. Le Shadow DOM garantit que les styles et la structure du composant sont encapsulés, offrant une intégration fluide avec React.

Conclusion

Maîtriser le Shadow DOM est essentiel pour le développement web moderne, car il offre une encapsulation puissante et une grande réutilisabilité. En comprenant et en mettant en œuvre les concepts et exemples fournis, vous pouvez créer des composants robustes et isolés qui améliorent la maintenabilité et l’évolutivité de vos applications web.

Ce guide complet devrait servir de base solide pour explorer et utiliser le Shadow DOM dans vos projets. Que vous construisiez de simples widgets ou des applications complexes, le Shadow DOM offre l’encapsulation et la flexibilité nécessaires pour garantir que vos composants restent isolés et faciles à gérer.

Practice

Which method is used to create a shadow root in JavaScript?

Trouvez-vous cela utile?

Aperçu dual-run — comparez avec les routes Symfony en production.