Custom Elements v1 - Componenti web riutilizzabili

Gli elementi personalizzati consentono agli sviluppatori web di definire nuovi tag HTML, estendere quelli esistenti e creare componenti web riutilizzabili.

Con Elementi personalizzati gli sviluppatori web possono creare nuovi tag HTML, integrare i tag HTML esistenti o estendere i componenti creati da altri sviluppatori. L'API è la base dei componenti web. Offre un metodo basato su standard web per creare componenti riutilizzabili utilizzando solo il codice JS/HTML/CSS di vanilla. Il risultato è meno codice, codice modulare e più riutilizzo nelle nostre app.

Introduzione

Il browser ci offre un eccellente strumento per strutturare le applicazioni web. Si chiama HTML. Forse ne hai sentito parlare. È dichiarativo, portabile, ben supportato e facile da usare. Per quanto sia grandioso il codice HTML, il suo vocabolario e la sua estensibilità sono limitati. Finora, lo standard HTML non ha sempre avuto un modo per associare automaticamente il comportamento JS al tuo markup.

Gli elementi personalizzati sono la risposta alla modernizzazione dell'HTML, al completamento dei pezzi mancanti e al raggruppamento della struttura con il comportamento. Se l'HTML non fornisce la soluzione a un problema, possiamo creare un elemento personalizzato che lo aiuti. Gli elementi personalizzati insegnano al browser nuovi trucchi, preservando al contempo i vantaggi dell'HTML.

Definizione di un nuovo elemento

Per definire un nuovo elemento HTML abbiamo bisogno della potenza di JavaScript.

L'elemento globale customElements viene utilizzato per definire un elemento personalizzato e indicare al browser la presenza di un nuovo tag. Richiama customElements.define() con il nome del tag che vuoi creare e un class JavaScript che estende HTMLElement di base.

Esempio: definizione di un riquadro a scomparsa per dispositivi mobili, <app-drawer>:

class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);

// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});

Esempio di utilizzo:

<app-drawer></app-drawer>

È importante ricordare che l'utilizzo di un elemento personalizzato non è diverso dall'utilizzo di un elemento <div> o di qualsiasi altro elemento. Le istanze possono essere dichiarate nella pagina, create dinamicamente in JavaScript, allegati listener di eventi e così via. Continua a leggere per ulteriori esempi.

Definizione dell'API JavaScript di un elemento

La funzionalità di un elemento personalizzato viene definita utilizzando un elemento ES2015 class che estende HTMLElement. L'estensione HTMLElement assicura che l'elemento personalizzato erediti l'intera API DOM e significa che tutte le proprietà e i metodi aggiunti alla classe diventano parte dell'interfaccia DOM dell'elemento. Essenzialmente, utilizza la classe per creare un'API JavaScript pubblica per il tag.

Esempio: viene definita l'interfaccia DOM di <app-drawer>:

class AppDrawer extends HTMLElement {

  // A getter/setter for an open property.
  get open() {
    return this.hasAttribute('open');
  }

  set open(val) {
    // Reflect the value of the open property as an HTML attribute.
    if (val) {
      this.setAttribute('open', '');
    } else {
      this.removeAttribute('open');
    }
    this.toggleDrawer();
  }

  // A getter/setter for a disabled property.
  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    // Reflect the value of the disabled property as an HTML attribute.
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Can define constructor arguments if you wish.
  constructor() {
    // If you define a constructor, always call super() first!
    // This is specific to CE and required by the spec.
    super();

    // Setup a click listener on <app-drawer> itself.
    this.addEventListener('click', e => {
      // Don't toggle the drawer if it's disabled.
      if (this.disabled) {
        return;
      }
      this.toggleDrawer();
    });
  }

  toggleDrawer() {
    // ...
  }
}

customElements.define('app-drawer', AppDrawer);

In questo esempio, stiamo creando un riquadro a scomparsa con una proprietà open, una proprietà disabled e un metodo toggleDrawer(). Inoltre, riflette le proprietà come attributi HTML.

Una caratteristica interessante degli elementi personalizzati è che this all'interno di una definizione di classe fa riferimento all'elemento DOM stesso, ovvero all'istanza della classe. Nel nostro esempio, this fa riferimento a <app-drawer>. Ecco come l'elemento può associare un listener click a se stesso. Inoltre, non devi limitarti ai listener di eventi. L'intera API DOM è disponibile all'interno del codice elemento. Utilizza this per accedere alle proprietà dell'elemento, ispezionarne gli elementi secondari (this.children), i nodi di query (this.querySelectorAll('.items')) e così via.

Regole per la creazione di elementi personalizzati

  1. Il nome di un elemento personalizzato deve contenere un trattino (-). Pertanto, <x-tags>, <my-element> e <my-awesome-app> sono tutti nomi validi, mentre <tabs> e <foo_bar> non lo sono. Questo requisito consente al parser HTML di distinguere gli elementi personalizzati dagli elementi normali. Garantisce inoltre la compatibilità in avanti quando nuovi tag vengono aggiunti al codice HTML.
  2. Non puoi registrare lo stesso tag più di una volta. Se si cerca di farlo, verrà lanciato un DOMException. Una volta comunicato al browser la presenza di un nuovo tag, il gioco è fatto. Nessun ritiro.
  3. Gli elementi personalizzati non possono chiudersi automaticamente perché il codice HTML consente la chiusura automatica solo di alcuni elementi. Scrivi sempre un tag di chiusura (<app-drawer></app-drawer>).

Reazioni di elementi personalizzati

Un elemento personalizzato può definire hook speciali del ciclo di vita per l'esecuzione del codice nei momenti più interessanti della sua esistenza. Queste sono chiamate reazioni degli elementi personalizzati.

Nome Chiamato quando
constructor Viene creata o eseguita l'upgrade di un'istanza dell'elemento. Utile per inizializzare lo stato, configurare i listener di eventi o creare uno shadow dom. Consulta le specifiche per le limitazioni su cosa puoi fare in constructor.
connectedCallback Chiamato ogni volta che l'elemento viene inserito nel DOM. Utile per eseguire il codice di configurazione, ad esempio il recupero delle risorse o il rendering. In genere, si dovrebbe provare a posticipare il lavoro fino a quel momento.
disconnectedCallback Chiamato ogni volta che l'elemento viene rimosso dal DOM. Utile per eseguire il codice di pulizia.
attributeChangedCallback(attrName, oldVal, newVal) Chiamato quando un attributo osservato viene aggiunto, rimosso, aggiornato o sostituito. Chiamato anche per i valori iniziali quando un elemento viene creato dal parser o eseguito l'upgrade. Nota: solo gli attributi elencati nella proprietà observedAttributes riceveranno questo callback.
adoptedCallback L'elemento personalizzato è stato spostato in un nuovo document (ad es. un elemento chiamato document.adoptNode(el)).

I callback delle reazioni sono sincroni. Se qualcuno chiama el.setAttribute() per il tuo elemento, il browser chiamerà immediatamente attributeChangedCallback(). Allo stesso modo, riceverai un disconnectedCallback() subito dopo la rimozione dell'elemento dal DOM (ad esempio l'utente chiama el.remove()).

Esempio:aggiunta di reazioni di elementi personalizzati a <app-drawer>:

class AppDrawer extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.
    // ...
  }

  connectedCallback() {
    // ...
  }

  disconnectedCallback() {
    // ...
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    // ...
  }
}

Definisci le reazioni se/quando hanno senso. Se il tuo elemento è sufficientemente complesso e apre una connessione a IndexedDB in connectedCallback(), esegui il lavoro di pulizia necessario in disconnectedCallback(). Ma fai attenzione. Non è possibile fare affidamento sulla rimozione dell'elemento dal DOM in tutte le circostanze. Ad esempio, disconnectedCallback() non verrà mai chiamato se l'utente chiude la scheda.

Proprietà e attributi

Riflettere le proprietà agli attributi

È comune che le proprietà HTML riflettano il loro valore nel DOM sotto forma di attributo HTML. Ad esempio, quando i valori di hidden o id vengono modificati in JS:

div.id = 'my-id';
div.hidden = true;

i valori vengono applicati al DOM in tempo reale come attributi:

<div id="my-id" hidden>

Questa procedura è chiamata "riflettere le proprietà negli attributi". Questa operazione è possibile in quasi tutte le proprietà in HTML. Perché? Gli attributi sono utili anche per configurare un elemento in modo dichiarativo e alcune API, come l'accessibilità e i selettori CSS, si basano sugli attributi per funzionare.

Riflettere una proprietà è utile ovunque tu voglia mantenere la rappresentazione DOM dell'elemento sincronizzata con il suo stato JavaScript. Uno dei motivi per cui potresti voler riflettere una proprietà è che gli stili definiti dall'utente vengano applicati quando lo stato JS cambia.

Ricorda <app-drawer>. Un consumer di questo componente potrebbe volerlo rendere in dissolvenza e/o impedire l'interazione dell'utente quando è disattivato:

app-drawer[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

Quando la proprietà disabled viene modificata in JS, vogliamo che l'attributo venga aggiunto al DOM in modo che il selettore dell'utente corrisponda. L'elemento può fornire questo comportamento riflettendo il valore in un attributo con lo stesso nome:

get disabled() {
  return this.hasAttribute('disabled');
}

set disabled(val) {
  // Reflect the value of `disabled` as an attribute.
  if (val) {
    this.setAttribute('disabled', '');
  } else {
    this.removeAttribute('disabled');
  }
  this.toggleDrawer();
}

Osservazione delle modifiche agli attributi

Gli attributi HTML consentono agli utenti di dichiarare facilmente lo stato iniziale:

<app-drawer open disabled></app-drawer>

Gli elementi possono reagire alle modifiche agli attributi definendo un attributeChangedCallback. Il browser chiamerà questo metodo per ogni modifica agli attributi elencati nell'array observedAttributes.

class AppDrawer extends HTMLElement {
  // ...

  static get observedAttributes() {
    return ['disabled', 'open'];
  }

  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Only called for the disabled and open attributes due to observedAttributes
  attributeChangedCallback(name, oldValue, newValue) {
    // When the drawer is disabled, update keyboard/screen reader behavior.
    if (this.disabled) {
      this.setAttribute('tabindex', '-1');
      this.setAttribute('aria-disabled', 'true');
    } else {
      this.setAttribute('tabindex', '0');
      this.setAttribute('aria-disabled', 'false');
    }
    // TODO: also react to the open attribute changing.
  }
}

Nell'esempio, stiamo impostando attributi aggiuntivi in <app-drawer> quando un attributo disabled viene modificato. Anche se non è una soluzione qui, puoi anche utilizzare attributeChangedCallback per mantenere la proprietà JS sincronizzata con il suo attributo.

Upgrade degli elementi

HTML migliorato progressivamente

Abbiamo già imparato che gli elementi personalizzati vengono definiti richiamando customElements.define(). Ma questo non significa che devi definire e registrare un elemento personalizzato in una volta sola.

Gli elementi personalizzati possono essere utilizzati prima della registrazione della relativa definizione.

Il miglioramento progressivo è una funzionalità degli elementi personalizzati. In altre parole, puoi dichiarare alcuni elementi <app-drawer> nella pagina e non richiamare più customElements.define('app-drawer', ...) se non in seguito. Questo perché il browser tratta i potenziali elementi personalizzati in modo diverso grazie a tag sconosciuti. Il processo di chiamata a define() e di fornire a un elemento esistente una definizione di classe è chiamato "upgrade degli elementi".

Per sapere quando viene definito il nome di un tag, puoi utilizzare window.customElements.whenDefined(). Restituisce una promessa che si risolve quando viene definito l'elemento.

customElements.whenDefined('app-drawer').then(() => {
  console.log('app-drawer defined');
});

Esempio: ritardo nel funzionamento fino all'upgrade di un insieme di elementi secondari

<share-buttons>
  <social-button type="twitter"><a href="...">Twitter</a></social-button>
  <social-button type="fb"><a href="...">Facebook</a></social-button>
  <social-button type="plus"><a href="...">G+</a></social-button>
</share-buttons>
// Fetch all the children of <share-buttons> that are not defined yet.
let undefinedButtons = buttons.querySelectorAll(':not(:defined)');

let promises = [...undefinedButtons].map((socialButton) => {
  return customElements.whenDefined(socialButton.localName);
});

// Wait for all the social-buttons to be upgraded.
Promise.all(promises).then(() => {
  // All social-button children are ready.
});

Contenuti definiti da elementi

Gli elementi personalizzati possono gestire i propri contenuti utilizzando le API DOM all'interno del codice. Le reazioni sono utili a questo scopo.

Esempio: crea un elemento con codice HTML predefinito:

customElements.define('x-foo-with-markup', class extends HTMLElement {
  connectedCallback() {
    this.innerHTML = "<b>I'm an x-foo-with-markup!</b>";
  }
  // ...
});

La dichiarazione di questo tag produce:

<x-foo-with-markup>
  <b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>

// TODO: DevSite - Esempio di codice rimosso poiché utilizzava gestori di eventi incorporati

Creazione di un elemento che utilizza Shadow DOM

Shadow DOM consente a un elemento di possedere, eseguire il rendering e applicare uno stile a un blocco di DOM separato dal resto della pagina. Potresti anche nascondere un'intera app con un solo tag:

<!-- chat-app's implementation details are hidden away in Shadow DOM. -->
<chat-app></chat-app>

Per utilizzare Shadow DOM in un elemento personalizzato, chiama this.attachShadow all'interno di constructor:

let tmpl = document.createElement('template');
tmpl.innerHTML = `
  <style>:host { ... }</style> <!-- look ma, scoped styles -->
  <b>I'm in shadow dom!</b>
  <slot></slot>
`;

customElements.define('x-foo-shadowdom', class extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to the element.
    let shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.appendChild(tmpl.content.cloneNode(true));
  }
  // ...
});

Esempio di utilizzo:

<x-foo-shadowdom>
  <p><b>User's</b> custom text</p>
</x-foo-shadowdom>

<!-- renders as -->
<x-foo-shadowdom>
  #shadow-root
  <b>I'm in shadow dom!</b>
  <slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>

Testo personalizzato dell'utente

// TODO: DevSite - Esempio di codice rimosso poiché utilizzava gestori di eventi incorporati

Creazione di elementi da un <template>

Per chi non lo conosce, l'elemento <template> consente di dichiarare frammenti del DOM che vengono analizzati, inerti al caricamento della pagina e possono essere attivati in un secondo momento durante il runtime. È un'altra primitiva API della famiglia dei componenti web. I modelli sono un segnaposto ideale per dichiarare la struttura di un elemento personalizzato.

Esempio: registrazione di un elemento con contenuti Shadow DOM creati da un <template>:

<template id="x-foo-from-template">
  <style>
    p { color: green; }
  </style>
  <p>I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
</template>

<script>
  let tmpl = document.querySelector('#x-foo-from-template');
  // If your code is inside of an HTML Import you'll need to change the above line to:
  // let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');

  customElements.define('x-foo-from-template', class extends HTMLElement {
    constructor() {
      super(); // always call super() first in the constructor.
      let shadowRoot = this.attachShadow({mode: 'open'});
      shadowRoot.appendChild(tmpl.content.cloneNode(true));
    }
    // ...
  });
</script>

Queste poche righe di codice sono davvero utili. Cerchiamo di capire gli aspetti principali in corso:

  1. Stiamo definendo un nuovo elemento in HTML: <x-foo-from-template>
  2. Il DOM shadow dell'elemento viene creato da un <template>
  3. Il DOM dell'elemento è locale rispetto all'elemento grazie al DOM Shadow
  4. Il CSS interno dell'elemento è limitato come ambito all'elemento grazie al DOM Shadow

Sono nel DOM di Shadow. Sul mio markup è stato stampato un <template>.

// TODO: DevSite - Esempio di codice rimosso poiché utilizzava gestori di eventi incorporati

Applicare uno stile a un elemento personalizzato

Anche se l'elemento definisce il proprio stile utilizzando Shadow DOM, gli utenti possono applicare uno stile all'elemento personalizzato dalla propria pagina. Questi sono chiamati "stili definiti dall'utente".

<!-- user-defined styling -->
<style>
  app-drawer {
    display: flex;
  }
  panel-item {
    transition: opacity 400ms ease-in-out;
    opacity: 0.3;
    flex: 1;
    text-align: center;
    border-radius: 50%;
  }
  panel-item:hover {
    opacity: 1.0;
    background: rgb(255, 0, 255);
    color: white;
  }
  app-panel > panel-item {
    padding: 5px;
    list-style: none;
    margin: 0 7px;
  }
</style>

<app-drawer>
  <panel-item>Do</panel-item>
  <panel-item>Re</panel-item>
  <panel-item>Mi</panel-item>
</app-drawer>

Forse ti starai chiedendo come funziona la specificità CSS se l'elemento ha stili definiti all'interno di Shadow DOM. In termini di specificità, prevalgono gli stili utente. Sostituiranno sempre gli stili definiti dagli elementi. Consulta la sezione sulla creazione di un elemento che utilizza Shadow DOM.

Pre-stile degli elementi non registrati

Prima di eseguire l'upgrade di un elemento, puoi sceglierlo come target in CSS utilizzando la pseudo-classe :defined. Questa opzione è utile per pre-stile di un componente. Ad esempio, potresti voler evitare che il layout o altri elementi visivi FOUC nascondano i componenti non definiti e li sbiadiscano quando vengono definiti.

Esempio: nascondi <app-drawer> prima che venga definito:

app-drawer:not(:defined) {
  /* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
  display: inline-block;
  height: 100vh;
  opacity: 0;
  transition: opacity 0.3s ease-in-out;
}

Una volta definito <app-drawer>, il selettore (app-drawer:not(:defined)) non corrisponde più.

Estensione di elementi

L'API Custom Elements è utile per creare nuovi elementi HTML, ma è utile anche per estendere altri elementi personalizzati o perfino l'HTML integrato del browser.

Estensione di un elemento personalizzato

L'estensione di un altro elemento personalizzato viene eseguita estendendo la definizione della sua classe.

Esempio: crea <fancy-app-drawer> che estenda <app-drawer>:

class FancyDrawer extends AppDrawer {
  constructor() {
    super(); // always call super() first in the constructor. This also calls the extended class' constructor.
    // ...
  }

  toggleDrawer() {
    // Possibly different toggle implementation?
    // Use ES2015 if you need to call the parent method.
    // super.toggleDrawer()
  }

  anotherMethod() {
    // ...
  }
}

customElements.define('fancy-app-drawer', FancyDrawer);

Estensione di elementi HTML nativi

Supponiamo che tu voglia creare un <button> più originale. Invece di replicare il comportamento e la funzionalità di <button>, una soluzione migliore consiste nel migliorare progressivamente l'elemento esistente utilizzando elementi personalizzati.

Un elemento integrato personalizzato è un elemento personalizzato che estende uno dei tag HTML integrati del browser. Il vantaggio principale dell'estensione di un elemento esistente è quello di ottenere tutte le sue funzionalità (proprietà DOM, metodi, accessibilità). Non esiste modo migliore per scrivere un'app web progressiva che migliorare progressivamente gli elementi HTML esistenti.

Per estendere un elemento, devi creare una definizione della classe che eredita dall'interfaccia DOM corretta. Ad esempio, un elemento personalizzato che si estende <button> deve ereditare da HTMLButtonElement anziché HTMLElement. Analogamente, un elemento che si estende <img> deve estendere HTMLImageElement.

Esempio - estensione di <button>:

// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
  constructor() {
    super(); // always call super() first in the constructor.
    this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
  }

  // Material design ripple animation.
  drawRipple(x, y) {
    let div = document.createElement('div');
    div.classList.add('ripple');
    this.appendChild(div);
    div.style.top = `${y - div.clientHeight/2}px`;
    div.style.left = `${x - div.clientWidth/2}px`;
    div.style.backgroundColor = 'currentColor';
    div.classList.add('run');
    div.addEventListener('transitionend', (e) => div.remove());
  }
}

customElements.define('fancy-button', FancyButton, {extends: 'button'});

Nota che la chiamata a define() cambia leggermente quando si estende un elemento nativo. Il terzo parametro obbligatorio indica al browser il tag che stai estendendo. Questo è necessario perché molti tag HTML condividono la stessa interfaccia DOM. <section>, <address> e <em> (tra gli altri) condividono tutti HTMLElement; sia <q> che <blockquote> condividono HTMLQuoteElement; e così via... Se specifichi {extends: 'blockquote'}, indichi al browser che stai creando un <blockquote> potenziato anziché un <q>. Consulta le specifiche HTML per l'elenco completo delle interfacce DOM dell'HTML.

I consumatori di un elemento integrato personalizzato possono utilizzarlo in diversi modi. Può dichiararla aggiungendo l'attributo is="" al tag nativo:

<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>

crea un'istanza in JavaScript:

// Custom elements overload createElement() to support the is="" attribute.
let button = document.createElement('button', {is: 'fancy-button'});
button.textContent = 'Fancy button!';
button.disabled = true;
document.body.appendChild(button);

oppure utilizza l'operatore new:

let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;

Ecco un altro esempio che estende <img>.

Esempio - estensione di <img>:

customElements.define('bigger-img', class extends Image {
  // Give img default size if users don't specify.
  constructor(width=50, height=50) {
    super(width * 10, height * 10);
  }
}, {extends: 'img'});

Gli utenti dichiarano questo componente come:

<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">

oppure crea un'istanza in JavaScript:

const BiggerImage = customElements.get('bigger-img');
const image = new BiggerImage(15, 20); // pass constructor values like so.
console.assert(image.width === 150);
console.assert(image.height === 200);

Dettagli vari

Confronto tra elementi sconosciuti ed elementi personalizzati non definiti

Il linguaggio HTML è flessibile e flessibile da utilizzare. Ad esempio, dichiari <randomtagthatdoesntexist> in una pagina e il browser è assolutamente felice di accettarla. Perché i tag non standard funzionano? La risposta è che la specifica HTML lo consente. Gli elementi non definiti dalla specifica vengono analizzati come HTMLUnknownElement.

Lo stesso non vale per gli elementi personalizzati. I potenziali elementi personalizzati vengono analizzati come HTMLElement se vengono creati con un nome valido (è incluso un segno "-"). Puoi verificarlo in un browser che supporta gli elementi personalizzati. Avvia la console: Ctrl+Maiusc+J (o Cmd+Opzione+J su Mac) e incolla le seguenti righe di codice:

// "tabs" is not a valid custom element name
document.createElement('tabs') instanceof HTMLUnknownElement === true

// "x-tabs" is a valid custom element name
document.createElement('x-tabs') instanceof HTMLElement === true

Riferimento API

Il valore globale customElements definisce metodi utili per lavorare con gli elementi personalizzati.

define(tagName, constructor, options)

Definisce un nuovo elemento personalizzato nel browser.

Esempio

customElements.define('my-app', class extends HTMLElement { ... });
customElements.define(
    'fancy-button', class extends HTMLButtonElement { ... }, {extends: 'button'});

get(tagName)

Se viene fornito un nome valido del tag dell'elemento personalizzato, restituisce il costruttore dell'elemento. Restituisce undefined se non è stata registrata alcuna definizione di elemento.

Esempio

let Drawer = customElements.get('app-drawer');
let drawer = new Drawer();

whenDefined(tagName)

Restituisce una Promessa che si risolve quando viene definito l'elemento personalizzato. Se l'elemento è già definito, risolvilo immediatamente. Rifiuta se il nome del tag non è un nome di elemento personalizzato valido.

Esempio

customElements.whenDefined('app-drawer').then(() => {
  console.log('ready!');
});

Supporto della cronologia e del browser

Se hai seguito i componenti web negli ultimi due anni, saprai che Chrome 36 e versioni successive ha implementato una versione dell'API Custom Elements che utilizza document.registerElement() anziché customElements.define(). Questa è ora considerata una versione deprecata dello standard, chiamata v0. customElements.define() è la nuova attrazione e quali fornitori di browser stanno iniziando a implementare. Si chiama Elementi personalizzati v1.

Se ti interessa la vecchia specifica v0, consulta l'articolo html5rocks.

Supporto del browser

Chrome 54 (stato), Safari 10.1 (stato) e Firefox 63 (stato) hanno Custom Elements v1. Edge è iniziato lo sviluppo.

Per rilevare elementi personalizzati tra le funzionalità, verifica l'esistenza di window.customElements:

const supportsCustomElementsV1 = 'customElements' in window;

Polyfill

Fino a quando il supporto dei browser non sarà ampiamente disponibile, è disponibile un polyfill autonomo per Custom Elements v1. Tuttavia, ti consigliamo di utilizzare il caricatore webcomponents.js per caricare in modo ottimale i polyfill dei componenti web. Il caricatore utilizza il rilevamento delle funzionalità per caricare in modo asincrono solo i pollyfill necessari richiesti dal browser.

Installala:

npm install --save @webcomponents/webcomponentsjs

Uso:

<!-- Use the custom element on the page. -->
<my-element></my-element>

<!-- Load polyfills; note that "loader" will load these async -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js" defer></script>

<!-- Load a custom element definitions in `waitFor` and return a promise -->
<script type="module">
  function loadScript(src) {
    return new Promise(function(resolve, reject) {
      const script = document.createElement('script');
      script.src = src;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }

  WebComponents.waitFor(() => {
    // At this point we are guaranteed that all required polyfills have
    // loaded, and can use web components APIs.
    // Next, load element definitions that call `customElements.define`.
    // Note: returning a promise causes the custom elements
    // polyfill to wait until all definitions are loaded and then upgrade
    // the document in one batch, for better performance.
    return loadScript('my-element.js');
  });
</script>

Conclusione

Gli elementi personalizzati offrono un nuovo strumento per definire nuovi tag HTML nel browser e creare componenti riutilizzabili. Combinandole con altre nuove primitive della piattaforma come Shadow DOM e <template>, iniziamo a realizzare il quadro generale di Web Componenti:

  • Cross-browser (standard web) per creare ed estendere componenti riutilizzabili.
  • Non richiede alcuna libreria o framework per iniziare. Vanilla JS/HTML FTW.
  • Forniscono un modello di programmazione familiare. Sono solo DOM/CSS/HTML.
  • Funziona bene con altre nuove funzionalità della piattaforma web (Shadow DOM, <template>, proprietà personalizzate CSS e così via).
  • Perfettamente integrato con gli strumenti DevTools del browser.
  • Sfrutta le funzioni di accessibilità esistenti.