Shadow DOM em JavaScript

Shadow DOM em JavaScript

Este artigo explica o Shadow DOM em 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>

Entendendo o Shadow DOM

Shadow DOM é uma funcionalidade poderosa do padrão Web Components que permite a encapsulação de estilos e da estrutura DOM dentro dos componentes. Essa funcionalidade impede a interferência de estilos e scripts entre os componentes e o documento principal.

Shadow DOM em JavaScript

Shadow DOM oferece uma maneira de criar uma árvore DOM com escopo associada a um elemento DOM regular. Essa árvore de sombra é isolada do documento geral, onde estilos e scripts externos não a influenciam, nem seus estilos e scripts internos vazam para fora.

Por exemplo, se você criar um componente de botão personalizado usando Shadow DOM, seus estilos não interferirão com outros elementos na página. Da mesma forma, elementos com o mesmo nome de classe não entrarão em conflito.

O conteúdo HTML comum fora do Shadow DOM é chamado de Light DOM.

Benefícios do Shadow DOM

  1. Encapsulamento

    • O Shadow DOM separa estilo e funcionalidade, impedindo conflitos com estilos e scripts globais.
  2. Reutilização

    • Componentes construídos com o Shadow DOM podem ser reutilizados em diferentes projetos sem se preocupar com conflitos de estilo.
  3. Manutenção

    • O encapsulamento torna a lógica e o estilo do componente autônomos, facilitando a depuração e a manutenção.

Criando o Shadow DOM

Para usar o Shadow DOM, você precisa anexar uma raiz de sombra a um elemento HTML. Aqui está um exemplo simples:.

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

Explicação

Este código inclui os seguintes elementos:.

  1. Elemento Host

    • Um elemento DOM comum ao qual a raiz do shadow é anexada (neste caso, #shadow-host).
  2. Raiz Shadow

    • A raiz da árvore shadow criada usando attachShadow.
  3. Modo

    • No modo open, JavaScript externo pode acessar a raiz shadow via element.shadowRoot. Por outro lado, o modo closed não permite acesso.

Estilizando dentro do Shadow DOM

O Shadow DOM possui seu próprio escopo de estilo. Os estilos definidos dentro da árvore do Shadow aplicam-se apenas aos elementos dentro dessa árvore. Aqui está um exemplo:.

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`;

Mesmo que haja estilos conflitantes no documento principal, eles não afetam o parágrafo dentro da árvore 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`;
  • O parágrafo dentro do Shadow DOM permanece verde, enquanto o externo é vermelho.

Eventos dentro do Shadow DOM

Eventos dentro do Shadow DOM são semelhantes aos eventos regulares do DOM, mas podem se comportar de maneira diferente em termos de propagação devido à encapsulação.

Aqui está um exemplo:.

 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});
  • Quando o botão é clicado, ambos os ouvintes são acionados, demonstrando o comportamento de propagação de eventos.
  • Quando um evento originado dentro do Shadow DOM sobe para o Light DOM, o target do evento é reescrito para o elemento host em vez da fonte original.
    • Neste exemplo, o event.target dentro do Shadow DOM é o elemento button real, mas fora do Shadow DOM, ele é substituído pelo elemento host div.

Shadow DOM e Elementos Personalizados

Shadow DOM e elementos personalizados são componentes chave de Web Components. São tecnologias usadas para criar componentes de interface reutilizáveis e 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);

O documento principal contém HTML assim:.

1<my-card></my-card>
  • Ao usar o Shadow DOM dentro de elementos personalizados, é possível criar componentes reutilizáveis que são resistentes a conflitos de estilo. Neste código, uma tag chamada my-card é criada e associada à classe MyCard. O elemento <p> dentro de my-card não é afetado por estilos externos e sempre é exibido em azul.

Slots: Distribuindo Conteúdo do Light DOM

Slots permitem projetar conteúdo do Light DOM dentro do Shadow DOM. Aqui está um exemplo:.

 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);

O documento principal contém HTML assim:.

1<my-element>
2    <h3 slot="header">Header Content</h1>
3    <p slot="content">Main Content</p>
4</my-element>
  • O elemento slot dentro do Shadow DOM exibe o conteúdo do Light DOM que possui o atributo slot correspondente.

Conclusão

O Shadow DOM é uma ferramenta essencial para construir componentes web robustos, reutilizáveis e de fácil manutenção. Ao encapsular estilos e funcionalidades, ele reduz o potencial de conflitos e simplifica a gestão do código.

Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.

YouTube Video