</> HTML5Advent
ENFRESDEITPT

// html · Web Platform Advent #5

Web Components: Custom Elements, Shadow DOM e template

Crea elementi HTML riutilizzabili con le API native dei Web Components — definire un Custom Element, incapsulare gli stili con lo Shadow DOM, clonare un <template> e passare dati con gli attributi.

Un mucchio di colorati mattoncini di plastica a incastro di forme diverse

I Web Components sono un insieme di API native del browser che ti permettono di creare i tuoi elementi HTML riutilizzabili — senza bisogno di un framework. Un componente che distribuisci funziona allo stesso modo in HTML puro, React, Vue o Svelte, perché è semplicemente un elemento che il browser comprende. Tre API lo rendono possibile: i Custom Elements, lo Shadow DOM e il tag <template>.

Custom Elements

Un Custom Element è una classe che estende HTMLElement, registrata con un nome di tag che deve contenere un trattino (così non potrà mai entrare in conflitto con un futuro tag integrato):

class GreetingCard extends HTMLElement {
  connectedCallback() {
    const p = document.createElement('p');
    p.textContent = 'Hello from a custom element!';
    this.append(p);
  }
}

customElements.define('greeting-card', GreetingCard);

Una volta definito, lo usi come qualsiasi altro tag:

<greeting-card></greeting-card>

La classe ti offre i callback del ciclo di vita. I più utili sono connectedCallback (l'elemento è stato aggiunto alla pagina), disconnectedCallback (è stato rimosso) e attributeChangedCallback (un attributo osservato è cambiato).

Reagire agli attributi

Per passare dati, leggi gli attributi — esattamente come src su un'immagine. Dichiara quali osservare con un getter statico observedAttributes, poi rispondi in attributeChangedCallback. Costruire il nodo con textContent mantiene al sicuro i valori forniti dall'utente:

class GreetingCard extends HTMLElement {
  static get observedAttributes() {
    return ['name'];
  }
  attributeChangedCallback(attr, oldVal, newVal) {
    if (attr === 'name') this.render();
  }
  connectedCallback() {
    this.render();
  }
  render() {
    const name = this.getAttribute('name') || 'friend';
    this.replaceChildren();
    const p = document.createElement('p');
    p.textContent = 'Hello, ' + name + '!';
    this.append(p);
  }
}
customElements.define('greeting-card', GreetingCard);

Ora <greeting-card name="Ada"></greeting-card> visualizza "Hello, Ada!", e cambiare l'attributo dal vivo lo ridisegna.

An open metal toolbox with neatly organised tools in separate compartments
Un web component è come un attrezzo in una cassetta degli attrezzi: un'unità autonoma ed etichettata che prendi e riutilizzi in molti progetti senza ricostruirla ogni volta.

Lo Shadow DOM: vero incapsulamento

Lo Shadow DOM conferisce a un elemento un sottoalbero privato i cui stili e markup sono isolati dal resto della pagina. Uno <style> al suo interno non può mai trapelare all'esterno, e il CSS esterno non può mai raggiungerlo per sbaglio. Costruiscilo con l'API del DOM e uno <slot> per il contenuto proiettato:

class FancyButton extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });

    const style = document.createElement('style');
    style.textContent =
      'button { background:#2563eb; color:#fff; border:0;' +
      ' border-radius:8px; padding:.6rem 1rem; }';

    const button = document.createElement('button');
    button.append(document.createElement('slot'));

    shadow.append(style, button);
  }
}
customElements.define('fancy-button', FancyButton);

Lo <slot> è un segnaposto: qualunque cosa tu metta tra i tag — <fancy-button>Save</fancy-button> — viene proiettata in quel punto, così il componente avvolge il tuo contenuto senza sostituirlo.

Template: clona una volta, riutilizza a basso costo

L'elemento <template> contiene markup inerte che viene analizzato ma non visualizzato finché non lo cloni. È il modo efficiente per produrre strutture ripetute:

<template id="row-tpl">
  <li><span class="label"></span></li>
</template>

<script>
  const tpl = document.getElementById('row-tpl');
  const node = tpl.content.cloneNode(true);
  node.querySelector('.label').textContent = 'Item 1';
  document.querySelector('ul').append(node);
</script>

I tre pezzi insieme

APITi offre
Custom ElementsI tuoi tag con callback del ciclo di vita
Shadow DOMIncapsulamento di stile e markup, slot
<template>Markup inerte riutilizzabile e clonabile

Raramente hai bisogno di tutti e tre insieme. Un semplice wrapper potrebbe usare solo un Custom Element; un widget stilizzato e autonomo combina un Custom Element con lo Shadow DOM e uno slot. Poiché il risultato è puro DOM, si inserisce in qualsiasi stack e sopravvive a qualunque framework fosse popolare nell'anno in cui l'hai scritto.