Shadow DOM en JavaScript

Shadow DOM en JavaScript

Este artículo explica el Shadow DOM en JavaScript.

YouTube Video

javascript-html-shadow-dom.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    h1, h2 {
 32        font-size: 1.2rem;
 33        color: #007bff;
 34        margin-top: 0.5em;
 35        margin-bottom: 0.5em;
 36        border-left: 5px solid #007bff;
 37        padding-left: 0.6em;
 38        background-color: #e9f2ff;
 39    }
 40
 41    button {
 42        display: block;
 43        margin: 1em auto;
 44        padding: 0.75em 1.5em;
 45        font-size: 1rem;
 46        background-color: #007bff;
 47        color: white;
 48        border: none;
 49        border-radius: 6px;
 50        cursor: pointer;
 51        transition: background-color 0.3s ease;
 52    }
 53
 54    button:hover {
 55        background-color: #0056b3;
 56    }
 57
 58    #output {
 59        margin-top: 1em;
 60        background-color: #1e1e1e;
 61        color: #0f0;
 62        padding: 1em;
 63        border-radius: 8px;
 64        min-height: 200px;
 65        font-family: Consolas, monospace;
 66        font-size: 0.95rem;
 67        overflow-y: auto;
 68        white-space: pre-wrap;
 69    }
 70
 71    .highlight {
 72        outline: 3px solid #ffc107; /* yellow border */
 73        background-color: #fff8e1;  /* soft yellow background */
 74        transition: background-color 0.3s ease, outline 0.3s ease;
 75    }
 76
 77    .active {
 78        background-color: #28a745; /* green background */
 79        color: #fff;
 80        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 81        transition: background-color 0.3s ease, box-shadow 0.3s ease;
 82    }
 83  </style>
 84</head>
 85<body>
 86    <div class="container">
 87        <h2>HTML Sample</h2>
 88        <div id="content"></div>
 89        <div id="shadow-host">Shadow Root Element</div>
 90        <my-card></my-card>
 91    </div>
 92
 93    <div class="container">
 94        <h1>JavaScript Console</h1>
 95        <button id="executeBtn">Execute</button>
 96        <div id="output"></div>
 97    </div>
 98
 99    <div>
100        <h2>Slot Sample</h2>
101        <my-element>
102            <h3 slot="header">Header Content</h1>
103            <p slot="content">Main Content</p>
104        </my-element>
105    </div>
106
107    <script>
108        // Override console.log to display messages in the #output element
109        (function () {
110            const originalLog = console.log;
111            console.log = function (...args) {
112                originalLog.apply(console, args);
113                const output = document.getElementById('output');
114                output.textContent += args.map(String).join(' ') + '\n';
115            };
116        })();
117
118        document.getElementById('executeBtn').addEventListener('click', () => {
119            // Prevent multiple loads
120            if (document.getElementById('externalScript')) return;
121
122            const script = document.createElement('script');
123            script.src = 'javascript-html-shadow-dom.js';
124            script.id = 'externalScript';
125            //script.onload = () => console.log('javascript-html-shadow-dom.js loaded and executed.');
126            //script.onerror = () => console.log('Failed to load javascript-html-shadow-dom.js.');
127            document.body.appendChild(script);
128        });
129    </script>
130</body>
131</html>

Entendiendo el Shadow DOM

El Shadow DOM es una poderosa característica del estándar de Web Components que permite la encapsulación de estilos y estructuras DOM dentro de los componentes. Esta característica evita la interferencia de estilos y scripts entre los componentes y el documento principal.

Shadow DOM en JavaScript

El Shadow DOM proporciona una forma de crear un árbol DOM limitado asociado con un elemento DOM regular. Este árbol en la sombra está aislado del documento general, donde los estilos y scripts externos no lo influyen, ni sus estilos y scripts internos se filtran.

Por ejemplo, si creas un componente de botón personalizado utilizando Shadow DOM, sus estilos no interferirán con otros elementos en la página. De manera similar, los elementos con el mismo nombre de clase no entrarán en conflicto.

El contenido HTML normal fuera del Shadow DOM se denomina Light DOM.

Beneficios del Shadow DOM

  1. Encapsulación

    • El Shadow DOM separa el estilo y la funcionalidad, evitando conflictos con estilos y scripts globales.
  2. Reusabilidad

    • Los componentes construidos con el Shadow DOM pueden reutilizarse en diferentes proyectos sin preocuparse por conflictos de estilos.
  3. Mantenibilidad

    • La encapsulación hace que la lógica y el estilo del componente sean independientes, facilitando la depuración y el mantenimiento.

Creando el Shadow DOM

Para usar el Shadow DOM, necesitas adjuntar una raíz de sombra a un elemento HTML. Aquí tienes un ejemplo sencillo:.

1// Select the host element
2const hostElement = document.querySelector('#shadow-host');
3
4// Attach a shadow root
5const shadowRoot = hostElement.attachShadow({ mode: 'open' });

Explicación

Este código incluye los siguientes elementos:.

  1. Elemento anfitrión

    • Un elemento DOM normal al cual se adjunta la raíz shadow (en este caso, #shadow-host).
  2. Raíz Shadow

    • La raíz del árbol shadow creada usando attachShadow.
  3. Modo

    • En modo open, el JavaScript externo puede acceder a la raíz shadow mediante element.shadowRoot. Por otro lado, el modo closed no permite el acceso.

Estilos dentro del Shadow DOM

Shadow DOM tiene su propio ámbito de estilos. Los estilos definidos dentro del árbol sombra solo se aplican a los elementos de ese árbol. Aquí hay un ejemplo:.

1// Add content to the shadow root
2shadowRoot.innerHTML = `
3    <style>
4        p {
5            color: green;
6        }
7    </style>
8    <p>Scoped style inside Shadow DOM.</p>
9`;

Incluso si hay estilos en conflicto en el documento principal, no afectan al párrafo dentro del árbol shadow.

1const content = document.getElementById('content');
2content.innerHTML = `
3    <style>
4        p {
5            color: red;
6        }
7    </style>
8    <p>This is in the main DOM.</p>
9`;
  • El párrafo dentro del Shadow DOM permanece verde, mientras que el externo es rojo.

Eventos dentro del Shadow DOM

Los eventos dentro del Shadow DOM son similares a los eventos normales del DOM pero pueden comportarse de manera diferente en términos de propagación debido a la encapsulación.

Aquí hay un ejemplo:.

 1// Add an event listener inside Shadow DOM
 2shadowRoot.innerHTML = `
 3    <div id="button-container">
 4        <button id="shadow-button">Click Me</button>
 5    </div>
 6`;
 7
 8shadowRoot.querySelector('#shadow-button').addEventListener('click', (event) => {
 9    console.log('Button : Button clicked inside Shadow DOM');
10    console.log(event.target);
11});
12
13shadowRoot.querySelector('#button-container').addEventListener('click', (event) => {
14    console.log('Container : Button clicked inside Shadow DOM');
15    console.log(event.target);
16});
17
18hostElement.addEventListener('click', (event) => {
19    console.log('Event bubbled to the host element');
20    console.log(event.target);
21});
22
23document.addEventListener('click', (event) => {
24    console.log('Document listener');
25    console.log(event.target);
26});
  • Cuando se hace clic en el botón, ambos listeners se activan, demostrando el comportamiento de la propagación de eventos.
  • Cuando un evento que se origina dentro del Shadow DOM se propaga hacia el Light DOM, el target del evento se reasigna al elemento host en lugar de la fuente original.
    • En este ejemplo, event.target dentro del Shadow DOM es el elemento button real, pero fuera del Shadow DOM se reemplaza por el elemento host div.

Shadow DOM y Elementos Personalizados

Shadow DOM y los elementos personalizados son componentes clave de los Web Components. Son tecnologías utilizadas para crear componentes de interfaz de usuario reutilizables y encapsulados.

 1class MyCard extends HTMLElement {
 2    constructor() {
 3        super();
 4        const shadow = this.attachShadow({ mode: 'open' });
 5        shadow.innerHTML = `
 6        <style>
 7            p {
 8                color: blue;
 9            }
10        </style>
11        <p>I'm inside shadow DOM</p>
12        `;
13    }
14}
15
16customElements.define('my-card', MyCard);

El documento principal contiene HTML como este:.

1<my-card></my-card>
  • Al utilizar el Shadow DOM dentro de elementos personalizados, puedes construir componentes reutilizables que resisten los conflictos de estilos. En este código, se crea una etiqueta llamada my-card que se asocia con la clase MyCard. El elemento <p> dentro de my-card no se ve afectado por estilos externos y siempre se muestra en azul.

Slots: Distribuyendo contenido del Light DOM

Los slots permiten proyectar el contenido del Light DOM dentro del Shadow DOM. Aquí hay un ejemplo:.

 1class MyElement extends HTMLElement {
 2    constructor() {
 3        super();
 4        const shadow = this.attachShadow({ mode: 'open' });
 5
 6        shadow.innerHTML = `
 7        <style>
 8            .container {
 9                border: 2px solid #ccc;
10                padding: 16px;
11                border-radius: 8px;
12                font-family: sans-serif;
13            }
14            .header {
15                font-size: 1.2em;
16                color: darkblue;
17                margin-bottom: 8px;
18            }
19            .content {
20                font-size: 1em;
21                color: #333;
22            }
23        </style>
24        <div class="container">
25            <div class="header">
26                <slot name="header"></slot>
27            </div>
28            <div class="content">
29                <slot name="content"></slot>
30            </div>
31        </div>
32        `;
33    }
34}
35
36customElements.define('my-element', MyElement);

El documento principal contiene HTML como este:.

1<my-element>
2    <h3 slot="header">Header Content</h1>
3    <p slot="content">Main Content</p>
4</my-element>
  • El elemento slot dentro del Shadow DOM muestra el contenido del Light DOM que tiene el atributo slot correspondiente.

Conclusión

Shadow DOM es una herramienta vital para construir componentes web robustos, reutilizables y mantenibles. Al encapsular estilos y funcionalidades, reduce el potencial de conflictos y simplifica la gestión de la base de código.

Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.

YouTube Video