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 gli elementi personalizzati, gli sviluppatori web possono creare nuovi tag HTML, migliorare i tag HTML esistenti o estendere i componenti utilizzati da altri sviluppatori autore. L'API è alla base del web componenti. Offre un approccio un metodo basato sugli standard per creare componenti riutilizzabili utilizzando vanilla JS/HTML/CSS. Il risultato è meno codice, codice modulare e maggiore riutilizzo le nostre app.

Introduzione

Il browser ci offre uno strumento eccellente per strutturare le applicazioni web. È chiamato HTML. Forse ne hai sentito parlare. È dichiarativo, portabile, supportati e facili da utilizzare. Per quanto l'HTML possa essere, il suo vocabolario e estensibilità limitate. Il HTML vivente standard ha sempre avuto un modo per associare automaticamente il comportamento JS al tuo markup... fino ad ora.

Gli elementi personalizzati sono la soluzione alla modernizzazione dell'HTML, riempiendo i le risorse e il raggruppamento della struttura con il comportamento. Se il codice HTML non fornisce a un problema, possiamo creare un elemento personalizzato che risolva il problema. Personalizzata insegnano nuovi trucchi al browser, preservando i vantaggi dell'HTML.

Definizione di un nuovo elemento

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

Il customElements globale viene utilizzato per definire un elemento personalizzato e per insegnare nel browser informazioni su un nuovo tag. Chiama customElements.define() con il nome del tag che vuoi creare e un class JavaScript che estende la HTMLElement di base.

Esempio. Come definire un riquadro a scomparsa di un dispositivo mobile <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 da utilizzando <div> o qualsiasi altro elemento. Le istanze possono essere dichiarate nella pagina, creati dinamicamente in JavaScript, è possibile collegare listener di eventi e così via. per altri esempi.

Definizione dell'API JavaScript di un elemento

La funzionalità di un elemento personalizzato viene definita utilizzando un modello ES2015 class che estende HTMLElement. L'estensione di HTMLElement assicura che l'elemento personalizzato eredita l'intera API DOM e significa qualsiasi proprietà/metodo che aggiungi diventano parte dell'interfaccia DOM dell'elemento. In pratica, usa la classe Creare un'API JavaScript pubblica per il tag.

Esempio - definizione dell'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, creiamo un riquadro a scomparsa con una proprietà open, disabled e un metodo toggleDrawer(). Inoltre riflette le proprietà come HTML attributi.

Una caratteristica interessante degli elementi personalizzati è che this all'interno di una definizione di classe si riferisce all'elemento DOM stesso, ovvero l'istanza della classe. Nel nostro Ad esempio, this si riferisce a <app-drawer>. Così (😉) l'elemento può collega un listener click a se stesso. Non devi limitarti agli ascoltatori di eventi. L'intera API DOM è disponibile all'interno del codice elemento. Usa this per accedere proprietà dell'elemento, controlla i relativi elementi secondari (this.children), 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 (-). Quindi <x-tags>, <my-element> e <my-awesome-app> sono tutti nomi validi, mentre <tabs> e <foo_bar> non lo sono. Questo requisito consente all'analizzatore sintattico HTML di distinguere gli elementi personalizzati da quelli normali. Inoltre, garantisce la distribuzione compatibilità quando vengono aggiunti nuovi tag al codice HTML.
  2. Non puoi registrare lo stesso tag più di una volta. Cercando di farlo, lancia DOMException. Una volta comunicate al browser un nuovo tag, li annotino. Non puoi riprenderti.
  3. Gli elementi personalizzati non possono chiudersi automaticamente perché l'HTML consente solo alcuni la chiusura automatica. Scrivi sempre un tag di chiusura (<app-drawer></app-drawer>).

Reazioni degli elementi personalizzati

Un elemento personalizzato può definire hook speciali del ciclo di vita per l'esecuzione del codice durante tempi interessanti della sua esistenza. Questi si chiamano elementi personalizzati di classificazione.

Nome Chiamata quando
constructor Un'istanza dell'elemento è creati o sottoposti ad upgrade. Utile per inizializzare lo stato, la configurazione di listener di eventi creando un dominio shadow. Consulta le spec . per conoscere le limitazioni relative a ciò che puoi fare nel constructor.
connectedCallback Viene chiamato ogni volta che viene inserito nel DOM. Utile per eseguire il codice di configurazione, ad esempio a recuperare risorse o eseguire il rendering. In genere, dovresti provare a ritardare il lavoro fino a questo momento.
disconnectedCallback Richiamato ogni volta che l'elemento viene rimosso dal DOM. Utile per: del codice per la pulizia.
attributeChangedCallback(attrName, oldVal, newVal) Richiamato quando un attributo osservato è stato aggiunti, rimossi, aggiornati o sostituiti. Chiamato anche per i valori iniziali quando un elemento viene creato dall'analizzatore sintattico, oppure upgrade eseguito. Nota: solo gli attributi elencati nella proprietà observedAttributes per ricevere la richiamata.
adoptedCallback La l'elemento personalizzato è stato spostato in un nuovo document (ad es. qualcuno ha chiamato document.adoptNode(el)).

I callback di reazione sono sincroni. Se qualcuno chiama el.setAttribute() sull'elemento, il browser chiamerà immediatamente attributeChangedCallback(). Allo stesso modo, riceverai un disconnectedCallback() subito dopo che l'elemento è rimosso dal DOM (ad es. 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 è opportuno. Se l'elemento è sufficientemente complesso e apre una connessione a IndexedDB in connectedCallback(), esegui le operazioni necessarie di pulizia in disconnectedCallback(). Ma fai attenzione! Non puoi fare affidamento rimosso 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 al DOM sotto forma di Attributo HTML. Ad esempio, se i valori di hidden o id vengono modificati in JS:

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

i valori vengono applicati al DOM live come attributi:

<div id="my-id" hidden>

Questo processo è noto come "riflettendo le proprietà delle attributi". Quasi tutte le proprietà nell'HTML lo fanno. Perché? Gli attributi sono utili anche la configurazione dichiarativa di un elemento e di alcune API come l'accessibilità e i CSS si basano sugli attributi per funzionare.

La riflessione di una proprietà è utile ovunque tu voglia conservare il DOM dell'elemento rappresentazione sincronizzabile con il suo stato JavaScript. Uno dei motivi per cui potresti voler riflettono una proprietà in modo che gli stili definiti dall'utente vengano applicati quando lo stato JS cambia.

Ricorda <app-drawer>. Un consumatore di questo componente potrebbe volerlo applicare una dissolvenza in uscita 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 sia aggiunti al DOM in modo che il selettore dell'utente corrisponda. L'elemento può fornire questo riflettendo il valore su 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 comodamente lo stato iniziale:

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

Gli elementi possono reagire alle modifiche degli 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.
  }
}

In questo esempio, stiamo impostando attributi aggiuntivi su <app-drawer> quando un L'attributo disabled è stato modificato. Anche se non lo stiamo facendo in questa sede, Inoltre, utilizza attributeChangedCallback per mantenere sincronizzata una proprietà JS con il suo .

Upgrade degli elementi

HTML progressivamente migliorato

Abbiamo già visto che gli elementi personalizzati vengono definiti customElements.define(). Ma questo non significa che sia necessario definire e registrare un elemento personalizzato contemporaneamente.

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

Il miglioramento progressivo è una funzionalità di elementi personalizzati. In altre parole, puoi dichiarare un gruppo di elementi <app-drawer> sulla pagina e non richiamare mai customElements.define('app-drawer', ...) fino a molto tempo dopo. Questo perché browser tratta i potenziali elementi personalizzati in modo diverso grazie a . La procedura per chiamare define() e aggiungere un componente un elemento con una definizione di classe è chiamato "upgrade elementi".

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

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

Esempio: ritarda il lavoro finché non viene eseguito l'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 dagli elementi

Gli elementi personalizzati possono gestire i propri contenuti utilizzando le API DOM all'interno . Le reazioni sono utili in questo caso.

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 genera:

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

// DA FARE: DevSite - Esempio di codice rimosso perché 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 persino nascondere un l'intera app all'interno di un singolo 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 del tuo 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

// DA FARE: DevSite - Esempio di codice rimosso perché utilizzava gestori di eventi incorporati

Creazione di elementi da un <template>

Per chi non ha familiarità, la <template> consente di dichiarare i frammenti del DOM che vengono analizzati, inerti al caricamento della pagina possono essere attivate in un secondo momento durante il runtime. È un'altra primitiva dell'API sul web di componenti della famiglia di componenti. I modelli sono un segnaposto ideale per dichiarare di un elemento personalizzato.

Esempio: registrazione di un elemento con contenuti DOM Shadow 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 hanno un impatto notevole. Cerchiamo di capire i principali aspetti su:

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

Sono nel DOM Ombra. Il mio markup è stato stampato da un <template>.

// DA FARE: DevSite - Esempio di codice rimosso perché utilizzava gestori di eventi incorporati

Definizione dello stile di un elemento personalizzato

Gli utenti possono anche usare Shadow DOM per definire uno stile proprio per l'elemento l'elemento personalizzato dalla loro 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 stai chiedendo come funziona la specificità CSS se l'elemento ha stili definiti in Shadow DOM. In termini di specificità, hanno la precedenza gli stili utente. Loro sostituisce sempre lo stile definito dall'elemento. Consulta la sezione Creazione di un elemento che utilizza Shadow DOM.

Pre-creazione dello stile di elementi non registrati

Prima di eseguire l'upgrade di un elemento, puoi sceglierlo come target in CSS utilizzando pseudo-classe :defined. È utile per applicare lo stile preliminare di un componente. Per Ad esempio, potresti voler impedire il layout o altri FOUC visivi nascondendo i componenti e il loro dissolvenza 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;
}

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

Estendere gli elementi

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

Estensione di un elemento personalizzato

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

Esempio. Crea <fancy-app-drawer> che estende <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ù fantasioso. Invece di replicare comportamento e funzionalità di <button>, un'opzione migliore è la distribuzione migliorare l'elemento esistente usando elementi personalizzati.

Un elemento integrato personalizzato è un elemento personalizzato che estende uno degli tag HTML integrati nel browser. Il vantaggio principale dell'estensione di un modello è avere tutte le sue caratteristiche (proprietà DOM, metodi, accessibilità). Non c'è modo migliore per scrivere un video piuttosto che migliorare progressivamente il codice HTML esistente elementi.

Per estendere un elemento, devi creare una definizione di classe che eredita dall'interfaccia DOM corretta. Ad esempio, un elemento personalizzato che si estende <button> deve ereditare da HTMLButtonElement anziché da HTMLElement. Allo stesso modo, un elemento che 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'});

Tieni presente che la chiamata a define() cambia leggermente quando estendi un'immagine . Il terzo parametro obbligatorio indica al browser quale tag stai in fase di estensione. Ciò è necessario perché molti tag HTML condividono lo stesso DOM a riga di comando. <section>, <address> e <em> (tra gli altri) condividono tutti HTMLElement; sia <q> che <blockquote> condividono HTMLQuoteElement; e così via... Se specifichi {extends: 'blockquote'}, il browser saprà che stai creando un <blockquote> denso invece di <q>. Consulta la documentazione spec per l'elenco completo delle interfacce DOM dell'HTML.

Gli utenti di un elemento integrato personalizzato possono utilizzarlo in diversi modi. Possono dichiaralo aggiungendo l'attributo is="" sul 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

Elementi sconosciuti ed elementi personalizzati non definiti

L'HTML è permissivo e flessibile da utilizzare. Ad esempio, dichiara <randomtagthatdoesntexist> su una pagina e il browser è perfettamente soddisfatto accettarlo. Perché i tag non standard funzionano? La risposta è HTML specifiche 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 sono stati creati con un nome valido (include un carattere "-"). Tu puoi verificarlo in un browser che supporta gli elementi personalizzati. Attiva la console: Ctrl+Maiusc+J (o Cmd+Opzione+J su Mac) e incolla nel 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

L'customElements globale definisce metodi utili per utilizzare i elementi.

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)

Fornito un nome valido per il 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 è già definito, risolvi immediatamente. Viene rifiutato se il nome del tag non è un nome di elemento personalizzato valido.

Esempio

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

Supporto per cronologia e browser

Se hai seguito i componenti web negli ultimi due anni, sappi che Chrome 36 e versioni successive ha implementato una versione dell'API Custom Elements che utilizza document.registerElement() anziché customElements.define(). È ora considerata una versione deprecata dello standard, chiamata v0. customElements.define() è la novità e quali sono i fornitori di browser la fase di implementazione. Si chiama Elementi personalizzati v1.

Se ti interessa la vecchia specifica v0, dai un'occhiata a html5rocks .

Supporto browser

Chrome 54 (stato), Safari 10.1 (stato) e Firefox 63 (stato) ha Elementi personalizzati v1. Edge è iniziato di sviluppo software.

Per rilevare le caratteristiche di elementi personalizzati, verifica l'esistenza di window.customElements:

const supportsCustomElementsV1 = 'customElements' in window;

Polyfill

Fino a quando il supporto dei browser non sarà disponibile su larga scala, c'è polyfill autonomo disponibile per Custom Elementi v1. Tuttavia, ti consigliamo di utilizzare il file webcomponents.js loader per caricare in modo ottimale i polyfill dei componenti web. Il caricatore utilizza il rilevamento delle caratteristiche per caricare in modo asincrono solo i pollyfill necessari richiesta dal browser.

Installalo:

npm install --save @webcomponents/webcomponentsjs

Utilizzo:

<!-- 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 creando componenti riutilizzabili. Combinale con la nuova piattaforma come Shadow DOM e <template>, e iniziamo a renderci conto della grande immagine dei componenti web:

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