Skip to content
> 💻 🧠 Codice 1001 > 🧪📓 Senza categoria > Shadow DOM — “DOM dentro il DOM”

Shadow DOM — “DOM dentro il DOM”

Certamente, ecco la traduzione in italiano:

Shadow DOM — “DOM dentro al DOM”

Shadow DOM — “DOM dentro al DOM”

Il DOM (Document Object Model) è un’interfaccia di programmazione (API) per il codice di una pagina che la rappresenta come una struttura ad albero di oggetti.

Ogni elemento HTML (ad esempio, <p>, <div>, <img>), ogni attributo e ogni pezzo di testo è un “nodo” separato in questo albero. Utilizzando JavaScript, possiamo interagire con questi nodi per modificare dinamicamente la pagina: cambiare testo, aggiungere stili, creare nuovi elementi o rimuovere quelli esistenti. In sostanza, il DOM è un modello “vivo” del documento con cui il codice interagisce.

Ma questa apertura ha un lato negativo. Quando creiamo un componente complesso e riutilizzabile (ad esempio, un lettore video personalizzato o un widget calendario), la sua struttura interna e i suoi stili diventano vulnerabili. Gli stili CSS della pagina principale possono accidentalmente “penetrare” nel componente e romperne l’aspetto. Allo stesso modo, il codice JavaScript della pagina può modificare involontariamente gli elementi interni del componente, interrompendone la logica.

Per risolvere questo problema, esiste lo Shadow DOM.

In sostanza, lo Shadow DOM è un “DOM dentro al DOM”. È un albero di elementi nascosto, collegato a un normale elemento della pagina (chiamato “host” o “ospite”), ma isolato dal DOM principale. Permette allo sviluppatore di creare un confine sigillato attorno alla struttura interna di un componente, proteggendolo dal mondo esterno.

Lo Shadow DOM consente di collegare alberi DOM nascosti a elementi nell’albero DOM regolare. Questo albero “ombra” inizia con una radice shadow (shadow root), sotto la quale è possibile aggiungere qualsiasi elemento, proprio come nel DOM normale.

Ci sono diversi termini chiave relativi allo Shadow DOM che dovresti conoscere. Lo Shadow host (o ospite) è un normale nodo del DOM a cui è collegato uno Shadow DOM. L’albero shadow (Shadow tree) è l’albero del DOM all’interno dello Shadow DOM. Il confine shadow (Shadow boundary) è il punto in cui finisce lo Shadow DOM e inizia il DOM regolare. Infine, la radice shadow (Shadow root) è il nodo radice dell’albero shadow.

DOM

È possibile manipolare i nodi all’interno dello Shadow DOM come si fa con i nodi normali. La differenza è che nessun codice all’interno dello Shadow DOM può influenzare nulla al di fuori di esso, garantendo un incapsulamento affidabile.

Prima che lo Shadow DOM diventasse disponibile per gli sviluppatori web, i browser lo utilizzavano già per incapsulare la struttura interna degli elementi standard. Ad esempio, l’elemento <video> con i controlli. Tutto ciò che vedi nel DOM è il tag <video>, ma esso contiene diversi pulsanti e altri controlli all’interno del suo Shadow DOM.

Creazione dello Shadow DOM

Lo Shadow DOM può essere creato sia in modo imperativo tramite JavaScript, sia in modo dichiarativo direttamente nell’HTML.

In modo imperativo con JavaScript

Questo metodo è perfetto per le applicazioni renderizzate dal client. Selezioniamo l’elemento host e invochiamo su di esso il metodo attachShadow().

<!-- Markup HTML -->
<div id="host"></div>
<span>Non sono nello Shadow DOM</span>
// Trova l'host e collega lo Shadow DOM
const host = document.querySelector("#host");
const shadow = host.attachShadow({ mode: "open" });

// Crea e aggiungi elementi all'albero shadow
const span = document.createElement("span");
span.textContent = "Sono nello Shadow DOM";
shadow.appendChild(span);

Il risultato sulla pagina sarà simile a questo:

Sono nello Shadow DOM
Non sono nello Shadow DOM

In modo dichiarativo con HTML

Per le applicazioni in cui il rendering lato server è importante, è possibile definire lo Shadow DOM in modo dichiarativo utilizzando un elemento <template> con l’attributo shadowrootmode.

<div id="host">
  <template shadowrootmode="open">
    <p>Questo paragrafo è all'interno dello Shadow DOM.</p>
    <style>
      p { color: red; } /* Questi stili saranno isolati */
    </style>
  </template>
</div>

Quando il browser elabora questo codice, crea automaticamente una radice shadow per il <div> e inserisce il contenuto del <template> al suo interno. Il tag <template> stesso scompare dall’albero DOM principale.

Incapsulamento: protezione da JavaScript e CSS

Il vantaggio principale dello Shadow DOM è l’isolamento. Vediamo come funziona.

Incapsulamento di JavaScript

Aggiungiamo un pulsante che cerca di modificare tutti gli elementi <span> sulla pagina.

// ... Codice di creazione dello Shadow DOM ...

const upper = document.querySelector("#upper-button");
upper.addEventListener("click", () => {
  // Questo selettore cerca in tutto il documento
  const spans = document.querySelectorAll("span");
  for (const span of spans) {
    span.textContent = span.textContent.toUpperCase();
  }
});

Quando si fa clic sul pulsante, solo lo <span> nel documento principale cambia. L’elemento all’interno dello Shadow DOM rimane invariato perché document.querySelectorAll() non può “guardare” oltre il confine dello shadow.

Accedere allo Shadow DOM: la proprietà shadowRoot e gli alberi nidificati

Quando chiamiamo host.attachShadow({ mode: "open" }), creiamo uno Shadow DOM “aperto”. Ciò significa che possiamo accedere al suo contenuto dall’esterno tramite host.shadowRoot.

// Trova gli span solo all'interno dell'albero shadow di un host specifico
const spansInShadow = host.shadowRoot.querySelectorAll("span");

Se si utilizza mode: "closed", host.shadowRoot restituisce null, e l’accesso esterno all’albero shadow è bloccato. Non si tratta di un meccanismo di sicurezza rigoroso, ma piuttosto di una convenzione per gli sviluppatori che indica che le parti interne del componente non dovrebbero essere toccate.

Lavorare con alberi shadow nidificati

In architetture di componenti complesse, un elemento personalizzato può contenerne altri, ognuno con il proprio Shadow DOM. Per accedere a un elemento in un albero shadow profondamente nidificato, è necessario attraversare ogni shadowRoot in sequenza.

Una struttura di esempio potrebbe essere: un componente <nmbrs-form> (il modulo principale), che al suo interno ha un <div> contenente un <nmbrs-button> (un pulsante personalizzato), e all’interno di questo <nmbrs-button> si trova un vero e proprio elemento <button> HTML.

Per accedere a questo pulsante dal contesto globale, il percorso è:

// 1. Trova il componente radice nel documento principale
const formComponent = document.querySelector('nmbrs-form');

// 2. Entra nel suo albero shadow
const shadowRoot1 = formComponent.shadowRoot;

// 3. Trova il componente pulsante nidificato
const buttonComponent = shadowRoot1.querySelector('div div.btn-container nmbrs-button');

// 4. Entra nell'albero shadow di questo componente
const shadowRoot2 = buttonComponent.shadowRoot;

// 5. Infine, trova l'elemento pulsante
const finalButton = shadowRoot2.querySelector('button#button');

Come una singola catena:

const button = document.querySelector('nmbrs-form').shadowRoot
                      .querySelector('div div.btn-container nmbrs-button').shadowRoot
                      .querySelector('button#button');

Questa lunga catena dimostra la potenza dell’incapsulamento: per raggiungere i dettagli interni, è necessario attraversare esplicitamente ogni “confine”. Questo rende il codice più prevedibile e protegge i componenti da modifiche accidentali.

Incapsulamento di CSS

Gli stili definiti nella pagina principale non influenzano gli elementi all’interno dello Shadow DOM.

/* Questo stile si applica solo agli span nel documento principale */
span {
  color: blue;
  border: 1px solid black;
}

Lo <span> all’interno dell’albero shadow non riceverà questi stili. Questo risolve il problema dei conflitti di stile accidentali.

Styling all’interno dello Shadow DOM

Gli stili definiti all’interno dell’albero shadow, a loro volta, non influenzano la pagina principale. Ci sono due modi principali per aggiungerli.

1. Fogli di stile costruibili (Constructable Stylesheets)

Questo metodo consente di creare un oggetto CSSStyleSheet in JavaScript e di applicarlo a uno o più alberi shadow. È efficiente se si hanno stili condivisi per molti componenti.

const sheet = new CSSStyleSheet();
sheet.replaceSync("span { color: red; border: 2px dotted black; }");

const shadow = host.attachShadow({ mode: "open" });
// Applica il foglio di stile alla radice shadow
shadow.adoptedStyleSheets = [sheet];
2. L’elemento <style>

Un modo semplice e dichiarativo è inserire un tag <style> direttamente all’interno dell’albero shadow (spesso all’interno di un <template>).

<template id="my-element">
  <style>
    span {
      color: red;
      border: 2px dotted black;
    }
  </style>
  <span>Sono nello Shadow DOM</span>
</template>```

#### Shadow DOM e Custom Elements: un'accoppiata perfetta

La piena potenza dello Shadow DOM si rivela durante la creazione di **Custom Elements** (Elementi Personalizzati). Senza incapsulamento, sarebbero estremamente fragili.

Un elemento personalizzato è una classe che estende `HTMLElement`. Tipicamente, l'elemento stesso agisce come host dello shadow, e la sua struttura interna viene creata all'interno dell'albero shadow.

Esempio di un semplice componente `<filled-circle>`:

javascript
class FilledCircle extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: “open” });

// Crea l'implementazione interna (es. un cerchio SVG)
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("r", "50");
circle.setAttribute("cx", "50");
circle.setAttribute("cy", "50");
// Il colore proviene dall'attributo dell'host
circle.setAttribute("fill", this.getAttribute("color"));

svg.appendChild(circle);
shadow.appendChild(svg);

}
}
customElements.define(“filled-circle”, FilledCircle);

Ora possiamo usarlo in HTML come un normale tag senza preoccuparci della sua struttura interna:

html


“`

Ognuno di questi componenti sarà completamente incapsulato e protetto dalla pagina esterna.

Leave a Reply

Your email address will not be published. Required fields are marked *