Custom Elements v1 – Composants Web réutilisables

Les éléments personnalisés permettent aux développeurs Web de définir de nouvelles balises HTML, d'étendre les balises existantes et de créer des composants Web réutilisables.

Avec les éléments personnalisés, les développeurs Web peuvent créer des balises HTML, renforcer les balises HTML existantes ou étendre les composants créés par d'autres développeurs. L'API est la base des composants Web. Il offre un moyen basé sur les normes Web de créer des composants réutilisables en utilisant uniquement du code JS/HTML/CSS standard. Cela permet de réduire le code, de le rendre modulaire et de le réutiliser davantage dans nos applications.

Introduction

Le navigateur est un excellent outil pour structurer les applications Web. Il s'agit du code HTML. Vous en avez peut-être déjà entendu parler. Il est déclaratif, portable, bien pris en charge et facile à utiliser. Le HTML est certes excellent, mais son vocabulaire et son extensibilité sont limités. La norme vivante HTML n'a jamais permis d'associer automatiquement le comportement JavaScript à votre balisage, jusqu'à présent.

Les éléments personnalisés sont la réponse à la modernisation du code HTML, au remplissage des éléments manquants et au regroupement de la structure avec le comportement. Si le code HTML ne fournit pas de solution à un problème, nous pouvons créer un élément personnalisé qui le fait. Les éléments personnalisés apprennent de nouvelles astuces au navigateur tout en préservant les avantages du code HTML.

Définir un nouvel élément

Pour définir un nouvel élément HTML, nous avons besoin de la puissance de JavaScript.

Le global customElements permet de définir un élément personnalisé et d'informer le navigateur d'une nouvelle balise. Appelez customElements.define() avec le nom de la balise que vous souhaitez créer et un class JavaScript qui étend la HTMLElement de base.

Exemple : définition d'un panneau de panneau de tiroir 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 {...});

Exemple d'utilisation :

<app-drawer></app-drawer>

N'oubliez pas que l'utilisation d'un élément personnalisé ne diffère pas de l'utilisation d'un <div> ou de tout autre élément. Les instances peuvent être déclarées sur la page, créées de manière dynamique en JavaScript, des écouteurs d'événements peuvent être associés, etc. Poursuivez votre lecture pour en savoir plus.

Définir l'API JavaScript d'un élément

La fonctionnalité d'un élément personnalisé est définie à l'aide d'un class ES2015 qui étend HTMLElement. L'extension de HTMLElement garantit que l'élément personnalisé hérite de l'ensemble de l'API DOM. Cela signifie que toutes les propriétés/méthodes que vous ajoutez à la classe font partie de l'interface DOM de l'élément. Utilisez essentiellement la classe pour créer une API JavaScript publique pour votre balise.

Exemple : définition de l'interface DOM de <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);

Dans cet exemple, nous créons un tiroir avec une propriété open, une propriété disabled et une méthode toggleDrawer(). Il reflète également les propriétés en tant qu'attributs HTML.

Une fonctionnalité intéressante des éléments personnalisés est que this dans une définition de classe fait référence à l'élément DOM lui-même, c'est-à-dire à l'instance de la classe. Dans notre exemple, this fait référence à <app-drawer>. C'est ainsi (😉) que l'élément peut associer un écouteur click à lui-même. Vous n'êtes pas limité aux écouteurs d'événements. L'intégralité de l'API DOM est disponible dans le code de l'élément. Utilisez this pour accéder aux propriétés de l'élément, inspecter ses enfants (this.children), interroger les nœuds (this.querySelectorAll('.items')), etc.

Règles concernant la création d'éléments personnalisés

  1. Le nom d'un élément personnalisé doit contenir un tiret (-). Par conséquent, <x-tags>, <my-element> et <my-awesome-app> sont tous des noms valides, tandis que <tabs> et <foo_bar> ne le sont pas. Cette exigence permet à l'analyseur HTML de distinguer les éléments personnalisés des éléments standards. Il assure également la compatibilité ascendante lorsque de nouvelles balises sont ajoutées au code HTML.
  2. Vous ne pouvez pas enregistrer la même balise plusieurs fois. Si vous essayez de le faire, une exception DOMException sera générée. Une fois que vous avez informé le navigateur d'une nouvelle balise, c'est terminé. Aucun retour.
  3. Les éléments personnalisés ne peuvent pas être autofermés, car le code HTML n'autorise que quelques éléments à être autofermés. Écrivez toujours une balise fermante (<app-drawer></app-drawer>).

Réactions d'éléments personnalisés

Un élément personnalisé peut définir des hooks de cycle de vie spéciaux pour exécuter du code à des moments intéressants de son existence. Ces éléments sont appelés réactions d'éléments personnalisés.

Nom Appelé lorsque
constructor Une instance de l'élément est créée ou mise à niveau. Utile pour initialiser l'état, configurer des écouteurs d'événements ou créer un DOM fantôme. Consultez la spécification pour connaître les restrictions applicables dans constructor.
connectedCallback Appelé chaque fois que l'élément est inséré dans le DOM. Utile pour exécuter du code de configuration, tel que la récupération de ressources ou le rendu. En règle générale, vous devez essayer de retarder le travail jusqu'à ce moment-là.
disconnectedCallback Appelé chaque fois que l'élément est supprimé du DOM. Utile pour exécuter du code de nettoyage.
attributeChangedCallback(attrName, oldVal, newVal) Appelé lorsqu'un attribut observé a été ajouté, supprimé, mis à jour ou remplacé. Appelé également pour les valeurs initiales lorsqu'un élément est créé par l'analyseur ou mis à niveau. Remarque:Seuls les attributs listés dans la propriété observedAttributes recevront ce rappel.
adoptedCallback L'élément personnalisé a été déplacé vers un nouvel élément document (par exemple, un élément appelé document.adoptNode(el)).

Les rappels de réaction sont synchrones. Si quelqu'un appelle el.setAttribute() sur votre élément, le navigateur appelle immédiatement attributeChangedCallback(). De même, vous recevrez un disconnectedCallback() juste après la suppression de votre élément du DOM (par exemple, l'utilisateur appelle el.remove()).

Exemple:ajouter des réactions d'éléments personnalisés à <app-drawer>:

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

  connectedCallback() {
    // ...
  }

  disconnectedCallback() {
    // ...
  }

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

Définissez des réactions si/lorsque cela a du sens. Si votre élément est suffisamment complexe et ouvre une connexion à IndexedDB dans connectedCallback(), effectuez le nettoyage nécessaire dans disconnectedCallback(). Mais attention ! Vous ne pouvez pas vous attendre à ce que votre élément soit supprimé du DOM dans toutes les circonstances. Par exemple, disconnectedCallback() n'est jamais appelé si l'utilisateur ferme l'onglet.

Propriétés et attributs

Refléter les propriétés dans les attributs

Il est courant que les propriétés HTML reflètent leur valeur dans le DOM en tant qu'attribut HTML. Par exemple, lorsque les valeurs de hidden ou id sont modifiées en JS:

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

Les valeurs sont appliquées au DOM en direct en tant qu'attributs:

<div id="my-id" hidden>

C'est ce qu'on appelle la réflexion des propriétés vers les attributs. Presque toutes les propriétés du code HTML fonctionnent de cette manière. Pourquoi ? Les attributs sont également utiles pour configurer un élément de manière déclarative, et certaines API telles que l'accessibilité et les sélecteurs CSS s'appuient sur des attributs pour fonctionner.

La réflexion d'une propriété est utile partout où vous souhaitez synchroniser la représentation DOM de l'élément avec son état JavaScript. Vous pouvez refléter une propriété pour que le style défini par l'utilisateur s'applique lorsque l'état JS change.

Consultez notre <app-drawer>. Un consommateur de ce composant peut vouloir l'estomper et/ou empêcher l'interaction utilisateur lorsqu'il est désactivé:

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

Lorsque la propriété disabled est modifiée en JS, nous souhaitons que cet attribut soit ajouté au DOM afin que le sélecteur de l'utilisateur corresponde. L'élément peut fournir ce comportement en reflétant la valeur dans un attribut du même nom:

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();
}

Observer les modifications apportées aux attributs

Les attributs HTML permettent aux utilisateurs de déclarer facilement l'état initial:

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

Les éléments peuvent réagir aux modifications d'attributs en définissant un attributeChangedCallback. Le navigateur appelle cette méthode pour chaque modification apportée aux attributs listés dans le tableau 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.
  }
}

Dans l'exemple, nous définissons des attributs supplémentaires sur <app-drawer> lorsqu'un attribut disabled est modifié. Bien que nous ne le fassions pas ici, vous pouvez également utiliser attributeChangedCallback pour synchroniser une propriété JavaScript avec son attribut.

Mises à niveau des éléments

HTML amélioré progressivement

Nous avons déjà appris que les éléments personnalisés sont définis en appelant customElements.define(). Cela ne signifie pas que vous devez définir et enregistrer un élément personnalisé en une seule fois.

Les éléments personnalisés peuvent être utilisés avant l'enregistrement de leur définition.

L'amélioration progressive est une fonctionnalité des éléments personnalisés. En d'autres termes, vous pouvez déclarer un ensemble d'éléments <app-drawer> sur la page et n'appeler jamais customElements.define('app-drawer', ...) avant bien plus tard. En effet, le navigateur traite les éléments personnalisés potentiels différemment grâce aux balises inconnues. Le processus d'appel de define() et d'attribution d'une définition de classe à un élément existant s'appelle "mise à niveau des éléments".

Pour savoir quand un nom de balise est défini, vous pouvez utiliser window.customElements.whenDefined(). Il renvoie une promesse qui se résout lorsque l'élément est défini.

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

Exemple : retarder la tâche jusqu'à ce qu'un ensemble d'éléments enfants soit mis à niveau

<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.
});

Contenu défini par des éléments

Les éléments personnalisés peuvent gérer leur propre contenu à l'aide des API DOM dans le code de l'élément. Les réactions sont très utiles pour cela.

Exemple : créez un élément avec du code HTML par défaut :

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

Déclarer cette balise génère les résultats suivants:

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

// TODO: DevSite - Exemple de code supprimé, car il utilisait des gestionnaires d'événements intégrés

Créer un élément qui utilise Shadow DOM

Le Shadow DOM permet à un élément de posséder, d'afficher et de styliser un fragment de DOM distinct du reste de la page. Vous pouvez même masquer une application entière dans une seule balise:

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

Pour utiliser Shadow DOM dans un élément personnalisé, appelez this.attachShadow dans votre 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));
  }
  // ...
});

Exemple d'utilisation :

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

Texte personnalisé de l'utilisateur

// TODO: DevSite - Exemple de code supprimé, car il utilisait des gestionnaires d'événements intégrés

Créer des éléments à partir d'un <template>

Pour les non-initiés, l'élément <template> vous permet de déclarer des fragments du DOM qui sont analysés, inertes au chargement de la page et peuvent être activés ultérieurement au moment de l'exécution. Il s'agit d'une autre primitive d'API de la famille des composants Web. Les modèles sont des espaces réservés idéaux pour déclarer la structure d'un élément personnalisé.

Exemple:enregistrement d'un élément avec du contenu Shadow DOM créé à partir d'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>

Ces quelques lignes de code sont très efficaces. Voyons les principaux éléments en jeu:

  1. Nous définissons un nouvel élément en HTML: <x-foo-from-template>
  2. Le Shadow DOM de l'élément est créé à partir d'un <template>.
  3. Le DOM de l'élément est local à l'élément grâce au Shadow DOM.
  4. Le CSS interne de l'élément est limité à l'élément grâce au Shadow DOM.

Je suis dans Shadow DOM. Mon balisage a été appliqué à partir d'un <template>.

// TODO: DevSite - Exemple de code supprimé, car il utilisait des gestionnaires d'événements intégrés

Appliquer un style à un élément personnalisé

Même si votre élément définit son propre style à l'aide de Shadow DOM, les utilisateurs peuvent styliser votre élément personnalisé à partir de leur page. Ces styles sont appelés "styles définis par l'utilisateur".

<!-- 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>

Vous vous demandez peut-être comment fonctionne la spécificité CSS si des styles sont définis dans Shadow DOM. En termes de spécificité, les styles utilisateur l'emportent. Ils remplacent toujours le style défini par l'élément. Consultez la section Créer un élément qui utilise Shadow DOM.

Pré-stylisation des éléments non enregistrés

Avant qu'un élément ne soit mis à niveau, vous pouvez le cibler en CSS à l'aide de la pseudo-classe :defined. Cela s'avère utile pour prédéfinir le style d'un composant. Par exemple, vous pouvez souhaiter éviter les erreurs de parcours utilisateur visuelles en masquant les composants non définis et en les faisant apparaître progressivement lorsqu'ils sont définis.

Exemple : masquez <app-drawer> avant qu'il ne soit défini :

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;
}

Une fois <app-drawer> défini, le sélecteur (app-drawer:not(:defined)) ne correspond plus.

Éléments extensibles

L'API Custom Elements est utile pour créer des éléments HTML, mais aussi pour étendre d'autres éléments personnalisés ou même le code HTML intégré du navigateur.

Étendre un élément personnalisé

Pour étendre un autre élément personnalisé, vous devez étendre sa définition de classe.

Exemple : créer un <fancy-app-drawer> qui étend <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);

Étendre les éléments HTML natifs

Supposons que vous souhaitiez créer un <button> plus élaboré. Au lieu de reproduire le comportement et les fonctionnalités de <button>, il est préférable d'améliorer progressivement l'élément existant à l'aide d'éléments personnalisés.

Un élément intégré personnalisé est un élément personnalisé qui étend l'une des balises HTML intégrées du navigateur. L'avantage principal d'étendre un élément existant est de bénéficier de toutes ses fonctionnalités (propriétés DOM, méthodes, accessibilité). Il n'existe pas de meilleure façon d'écrire une application Web progressive que d'améliorer progressivement les éléments HTML existants.

Pour étendre un élément, vous devez créer une définition de classe qui hérite de l'interface DOM appropriée. Par exemple, un élément personnalisé qui étend <button> doit hériter de HTMLButtonElement au lieu de HTMLElement. De même, un élément qui étend <img> doit étendre HTMLImageElement.

Exemple : étendre <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'});

Notez que l'appel de define() change légèrement lorsque vous étendez un élément natif. Le troisième paramètre obligatoire indique au navigateur la balise que vous étendez. Cela est nécessaire, car de nombreuses balises HTML partagent la même interface DOM. <section>, <address> et <em> (entre autres) partagent tous HTMLElement. <q> et <blockquote> partagent HTMLQuoteElement, etc. Spécifier {extends: 'blockquote'} permet au navigateur de savoir que vous créez un <blockquote> amélioré au lieu d'un <q>. Consultez la spécification HTML pour obtenir la liste complète des interfaces DOM du HTML.

Les utilisateurs d'un élément intégré personnalisé peuvent l'utiliser de plusieurs façons. Ils peuvent le déclarer en ajoutant l'attribut is="" à la balise native:

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

Créez une instance en 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);

ou utiliser l'opérateur new:

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

Voici un autre exemple qui étend <img>.

Exemple : étendre <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'});

Les utilisateurs déclarent ce composant comme suit:

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

ou créez une instance en 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);

Divers détails

Éléments inconnus par rapport aux éléments personnalisés non définis

Le code HTML est souple et tolérant. Par exemple, déclarez <randomtagthatdoesntexist> sur une page, et le navigateur l'acceptera sans problème. Pourquoi les balises non standards fonctionnent-elles ? La réponse est que la spécification HTML l'autorise. Les éléments qui ne sont pas définis par la spécification sont analysés en tant que HTMLUnknownElement.

Il n'en va pas de même pour les éléments personnalisés. Les éléments personnalisés potentiels sont analysés en tant que HTMLElement s'ils sont créés avec un nom valide (qui inclut un "-"). Vous pouvez le vérifier dans un navigateur compatible avec les éléments personnalisés. Lancez la console : Ctrl+Maj+J (ou Cmd+Opt+J sur Mac), puis collez les lignes de code suivantes:

// "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

Documentation de référence de l'API

Le global customElements définit des méthodes utiles pour travailler avec des éléments personnalisés.

define(tagName, constructor, options)

Définit un nouvel élément personnalisé dans le navigateur.

Exemple

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

get(tagName)

À partir d'un nom de balise d'élément personnalisé valide, renvoie le constructeur de l'élément. Renvoie undefined si aucune définition d'élément n'a été enregistrée.

Exemple

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

whenDefined(tagName)

Renvoie une promesse qui se résout lorsque l'élément personnalisé est défini. Si l'élément est déjà défini, corrigez-le immédiatement. Rejette si le nom de la balise n'est pas un nom d'élément personnalisé valide.

Exemple

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

Prise en charge de l'historique et des navigateurs

Si vous suivez les composants Web depuis quelques années, vous savez que Chrome 36 et versions ultérieures ont implémenté une version de l'API Custom Elements qui utilise document.registerElement() au lieu de customElements.define(). Cette version est désormais considérée comme obsolète et est appelée v0. customElements.define() est la nouvelle tendance et ce que les fournisseurs de navigateurs commencent à implémenter. Il s'agit de Custom Elements v1.

Si l'ancienne spécification v0 vous intéresse, consultez l'article html5rocks.

Prise en charge des navigateurs

Chrome 54 (état), Safari 10.1 (état) et Firefox 63 (état) sont compatibles avec la version 1 des éléments personnalisés. Le développement d'Edge a commencé.

Pour détecter des éléments personnalisés, vérifiez l'existence de window.customElements:

const supportsCustomElementsV1 = 'customElements' in window;

Polyfill

Tant que la compatibilité avec les navigateurs n'est pas largement disponible, un polyfill autonome est disponible pour les éléments personnalisés v1. Toutefois, nous vous recommandons d'utiliser le chargeur webcomponents.js pour charger de manière optimale les polyfills des composants Web. Le chargeur utilise la détection de fonctionnalités pour ne charger de manière asynchrone que les remplissages de polymères nécessaires requis par le navigateur.

Installez-le:

npm install --save @webcomponents/webcomponentsjs

Utilisation :

<!-- 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>

Conclusion

Les éléments personnalisés nous offrent un nouvel outil pour définir de nouvelles balises HTML dans le navigateur et créer des composants réutilisables. En les combinant aux autres nouvelles primitives de plate-forme telles que Shadow DOM et <template>, nous commençons à comprendre l'ensemble des Web Components:

  • Inter-navigateur (norme Web) pour créer et étendre des composants réutilisables.
  • Aucune bibliothèque ni aucun framework n'est nécessaire pour commencer. JS/HTML vanille, c'est la vie !
  • Fournit un modèle de programmation familier. Il s'agit simplement de DOM/CSS/HTML.
  • Fonctionne bien avec les autres nouvelles fonctionnalités de la plate-forme Web (Shadow DOM, <template>, propriétés personnalisées CSS, etc.)
  • Intégration étroite avec les outils de développement du navigateur.
  • Exploitez les fonctionnalités d'accessibilité existantes.