W3docs

Elementi Personalizzati

Scopri come creare Custom Elements in JavaScript: definire una classe, registrarla con customElements.define(), usare i callback del ciclo di vita, osservare attributi ed estendere elementi built-in.

Gli elementi personalizzati (Custom Elements) sono uno dei pilastri fondamentali dei Web Components. Permettono di definire tag HTML personalizzati supportati da una classe JavaScript, estendendo il vocabolario integrato del browser con elementi riutilizzabili e autonomi che includono la propria struttura, stile e comportamento.

Questa pagina copre tutto ciò che serve per creare un elemento personalizzato: come definirlo e registrarlo, i callback del ciclo di vita che il browser invoca automaticamente, come reagire alle modifiche degli attributi, come estendere gli elementi built-in e le pratiche che rendono i componenti robusti e accessibili.

Due tipi di elementi personalizzati

La specifica definisce due varianti, e la distinzione influisce sul modo in cui vengono creati e utilizzati:

  • Elementi personalizzati autonomi: estendono il generico HTMLElement e vengono usati come nuovi tag: <my-card></my-card>. Questo è il caso più comune.
  • Elementi built-in personalizzati: estendono una classe built-in specifica (come HTMLButtonElement) e vengono usati con l'attributo is: <button is="fancy-button">. Ereditano gratuitamente l'accessibilità e il comportamento dell'elemento ospitante.

Una regola si applica a entrambi: il nome del tag deve contenere un trattino (my-card, non mycard). Il trattino indica al parser che il tag è un elemento personalizzato e previene conflitti con futuri tag standard.

Definire un elemento personalizzato

Per creare un elemento personalizzato autonomo, definisci una class che estende la classe built-in HTMLElement, poi registrala nel browser con customElements.define(tagName, class). La classe incapsula il comportamento dell'elemento; la registrazione lo associa a un nome di tag.

Un pattern comune consiste nel costruire il DOM interno dell'elemento all'interno di un shadow DOM in modo che il markup e gli stili siano isolati dal resto della pagina.

Esempio: Creare un semplice elemento personalizzato

<my-custom-element></my-custom-element>
<script>
  class MyCustomElement extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' });
      this.shadowRoot.innerHTML = `<p>Hello, World!</p>`;
    }
  }
  
  customElements.define('my-custom-element', MyCustomElement);
</script>

Questo esempio definisce un elemento personalizzato chiamato my-custom-element che mostra "Hello, World!" all'interno di un shadow DOM. Per utilizzarlo, basta aggiungere <my-custom-element></my-custom-element> in qualsiasi punto dell'HTML. Nota che gli elementi personalizzati non hanno forma auto-chiudente — scrivi sempre il tag di chiusura corrispondente.

Nota

Custom Elements v1 è supportato in tutti i browser moderni (Chrome 54+, Firefox 52+, Safari 10.1+, Edge 79+). Verifica sempre la compatibilità del browser se stai sviluppando per ambienti legacy.

Callback del ciclo di vita

Gli elementi personalizzati dispongono di un insieme di callback del ciclo di vita che consentono agli sviluppatori di eseguire codice in momenti specifici della vita dell'elemento:

  • connectedCallback(): Richiamato ogni volta che l'elemento personalizzato viene aggiunto a un elemento connesso al documento.
  • disconnectedCallback(): Richiamato ogni volta che l'elemento personalizzato viene disconnesso dal DOM del documento.
  • attributeChangedCallback(name, oldValue, newValue): Richiamato ogni volta che uno degli attributi dell'elemento personalizzato viene aggiunto, rimosso o modificato.
  • adoptedCallback(): Richiamato ogni volta che l'elemento personalizzato viene spostato in un nuovo documento.
CallbackQuando viene attivato
connectedCallback()L'elemento viene aggiunto al DOM
disconnectedCallback()L'elemento viene rimosso dal DOM
attributeChangedCallback(name, oldValue, newValue)Un attributo osservato cambia
adoptedCallback()L'elemento viene spostato in un nuovo documento

Un modello mentale utile: il costruttore viene eseguito una sola volta quando l'istanza dell'elemento viene creata (prima che sia nel DOM, quindi non deve accedere ad attributi o figli), mentre connectedCallback può essere eseguito più volte se l'elemento viene aggiunto, rimosso e ri-aggiunto. Esegui le operazioni che dipendono dal DOM in connectedCallback, e pulisci i listener o i timer in disconnectedCallback per evitare perdite di memoria.

Esempio: Utilizzo dei callback del ciclo di vita

<lifecycle-element></lifecycle-element>
<script>
class LifecycleElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        #status {
          color: blue;
          font-weight: bold;
        }
      </style>
      <p>Lifecycle Element</p>
      <p id="status">Element not connected</p>
    `;
  }

  connectedCallback() {
    this.shadowRoot.getElementById('status').textContent = 'Element connected to the page.';
  }

  disconnectedCallback() {
    this.shadowRoot.getElementById('status').textContent = 'Element disconnected from the page.';
  }
}

customElements.define('lifecycle-element', LifecycleElement);
</script>

Attributi e proprietà

Gli elementi personalizzati possono avere attributi e proprietà per gestire il proprio stato e comportamento. Gli attributi vengono impostati direttamente in HTML e sono sempre stringhe, mentre le proprietà vengono impostate sull'oggetto DOM dell'elemento e possono essere di qualsiasi tipo di dato.

Il dettaglio fondamentale è attributeChangedCallback: viene attivato solo per gli attributi esplicitamente elencati nel getter static get observedAttributes() dell'elemento. Se un attributo non è presente in quell'array, modificarlo non attiva alcun callback. Una convenzione comune consiste nell'esporre una proprietà getter/setter che si riflette semplicemente su un attributo, in modo che il codice JavaScript e l'HTML rimangano sincronizzati.

Esempio: Gestire attributi e proprietà

<attribute-element id="element" data-content="Initial content"></attribute-element>
<button onclick="buttonClicked()">Click to change attribute</button>
<script>
  class AttributeElement extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' });
      this.shadowRoot.innerHTML = `<p>Attribute Example: <span id="content"></span></p>`;
    }
  
    static get observedAttributes() {
      return ['data-content'];
    }
  
    attributeChangedCallback(name, oldValue, newValue) {
      if (name === 'data-content') {
        this.shadowRoot.getElementById('content').textContent = newValue;
      }
    }
  
    set content(value) {
      this.setAttribute('data-content', value);
    }
  
    get content() {
      return this.getAttribute('data-content');
    }
  }
  
  customElements.define('attribute-element', AttributeElement);

  function buttonClicked() {
    alert('button clicked!');
    const ourCustomElement = document.getElementById('element');
    ourCustomElement.content = 'New content';
  }
</script>

In questo esempio, attribute-element aggiorna il proprio contenuto in base all'attributo data-content. La proprietà content fornisce un modo comodo per ottenere e impostare questo attributo a livello di codice.

Estendere gli elementi built-in

Gli elementi built-in personalizzati estendono una classe built-in specifica e vengono usati con l'attributo is. Il grande vantaggio è che ereditano la semantica e l'accessibilità dell'elemento ospitante — un <button is="fancy-button"> rimane un vero pulsante per la tastiera e per gli screen reader.

Esempio: Estendere un elemento built-in

<button is="fancy-button">Click me!</button>
<script>
  class FancyButton extends HTMLButtonElement {
    constructor() {
      super();
      this.addEventListener('click', () => {
        alert('Fancy button clicked!');
      });
    }
  }
  
  customElements.define('fancy-button', FancyButton, { extends: 'button' });
</script>

Qui, fancy-button estende l'elemento standard <button>, aggiungendo un messaggio di avviso quando il pulsante viene cliccato. Il terzo argomento di customElements.define{ extends: 'button' } — indica al browser a quale tag si applica questo elemento personalizzato.

Nota

Safari non supporta gli elementi built-in personalizzati (la forma is=). Per una compatibilità più ampia, preferisci gli elementi personalizzati autonomi e re-implementa l'accessibilità necessaria, oppure carica un polyfill.

Buone pratiche per gli elementi personalizzati

  1. Utilizza lo Shadow DOM: Incapsula sempre la struttura interna e gli stili del tuo elemento personalizzato usando lo Shadow DOM.
  2. Definisci API chiare: Fornisci API chiare e intuitive per i tuoi elementi personalizzati attraverso attributi e proprietà ben documentati.
  3. Gestione del ciclo di vita: Gestisci correttamente i callback del ciclo di vita dell'elemento per garantire un comportamento robusto e prevenire perdite di memoria.
  4. Accessibilità: Assicurati che i tuoi elementi personalizzati siano accessibili includendo ruoli e proprietà ARIA appropriati.
  5. Test: Testa accuratamente i tuoi elementi personalizzati su diversi browser e ambienti per garantire compatibilità e stabilità.

Conclusione

Gli elementi personalizzati offrono un modo potente per estendere HTML, consentendo la creazione di componenti riutilizzabili e incapsulati con comportamento personalizzato. Sfruttando le funzionalità degli elementi personalizzati, inclusi i callback del ciclo di vita, gli attributi, le proprietà e lo Shadow DOM, gli sviluppatori possono creare applicazioni web sofisticate e manutenibili.

Inizia a sperimentare con gli elementi personalizzati nei tuoi progetti oggi stesso e sblocca nuove possibilità per lo sviluppo web. Gli esempi forniti qui sono solo un punto di partenza — usali come base per creare i tuoi innovativi elementi personalizzati.

Argomenti correlati

Esercitazione

Pratica
Quali delle seguenti affermazioni sugli elementi personalizzati in JavaScript sono vere?
Quali delle seguenti affermazioni sugli elementi personalizzati in JavaScript sono vere?
Was this page helpful?