Éléments Personnalisés
Apprenez à créer des éléments personnalisés en JavaScript : définir une classe, l'enregistrer avec customElements.define(), utiliser les callbacks de cycle de vie, observer les attributs et étendre les éléments natifs.
Les éléments personnalisés (Custom Elements) sont l'un des piliers fondamentaux des Web Components. Ils vous permettent de définir vos propres balises HTML adossées à une classe JavaScript, enrichissant ainsi le vocabulaire natif du navigateur avec des éléments réutilisables et autonomes qui portent leur propre structure, style et comportement.
Cette page couvre tout ce dont vous avez besoin pour créer un élément personnalisé : comment le définir et l'enregistrer, les callbacks de cycle de vie que le navigateur appelle pour vous, comment réagir aux changements d'attributs, comment étendre des éléments natifs, et les bonnes pratiques qui rendent vos composants robustes et accessibles.
Deux types d'éléments personnalisés
La spécification définit deux variantes, et la distinction influe sur la façon de les créer et de les utiliser :
- Les éléments personnalisés autonomes étendent la classe générique
HTMLElementet s'utilisent en tant que nouvelles balises :<my-card></my-card>. C'est le cas le plus courant. - Les éléments natifs personnalisés étendent une classe native spécifique (telle que
HTMLButtonElement) et s'utilisent avec l'attributis:<button is="fancy-button">. Ils héritent gratuitement de l'accessibilité et du comportement de l'élément hôte.
Une règle s'applique aux deux : le nom de balise doit contenir un trait d'union (my-card, et non mycard). Le trait d'union indique au parseur que la balise est un élément personnalisé et évite les collisions avec de futures balises standard.
Définir un élément personnalisé
Pour créer un élément personnalisé autonome, définissez une class qui étend la classe native HTMLElement, puis enregistrez-la auprès du navigateur avec customElements.define(tagName, class). La classe encapsule le comportement de l'élément ; l'enregistrement le relie à un nom de balise.
Un modèle courant consiste à construire le DOM interne de l'élément dans un shadow DOM afin que son balisage et ses styles soient isolés du reste de la page.
Exemple : créer un élément personnalisé simple
<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>Cet exemple définit un élément personnalisé nommé my-custom-element qui affiche « Hello, World! » à l'intérieur d'un shadow DOM. Pour l'utiliser, ajoutez simplement <my-custom-element></my-custom-element> n'importe où dans votre HTML. Notez que les éléments personnalisés n'ont pas de forme auto-fermante — écrivez toujours la balise de fermeture correspondante.
Custom Elements v1 est pris en charge par tous les navigateurs modernes (Chrome 54+, Firefox 52+, Safari 10.1+, Edge 79+). Vérifiez toujours la compatibilité navigateur si vous ciblez des environnements anciens.
Callbacks de cycle de vie
Les éléments personnalisés disposent d'un ensemble de callbacks de cycle de vie qui permettent aux développeurs d'exécuter du code à des moments précis du cycle de vie de l'élément :
connectedCallback(): invoqué à chaque fois que l'élément personnalisé est ajouté à un élément connecté à un document.disconnectedCallback(): invoqué à chaque fois que l'élément personnalisé est déconnecté du DOM du document.attributeChangedCallback(name, oldValue, newValue): invoqué à chaque fois qu'un des attributs de l'élément personnalisé est ajouté, supprimé ou modifié.adoptedCallback(): invoqué à chaque fois que l'élément personnalisé est déplacé vers un nouveau document.
| Callback | Quand il se déclenche |
|---|---|
connectedCallback() | L'élément est ajouté au DOM |
disconnectedCallback() | L'élément est retiré du DOM |
attributeChangedCallback(name, oldValue, newValue) | Un attribut observé change |
adoptedCallback() | L'élément est déplacé vers un nouveau document |
Un modèle mental utile : le constructeur s'exécute une seule fois lors de la création de l'instance de l'élément (avant qu'il soit dans le DOM, il ne doit donc pas toucher aux attributs ni aux enfants), tandis que connectedCallback peut s'exécuter plusieurs fois si l'élément est ajouté, supprimé, puis réajouté. Effectuez la configuration dépendant du DOM dans connectedCallback, et nettoyez les écouteurs d'événements ou les minuteurs dans disconnectedCallback pour éviter les fuites mémoire.
Exemple : utiliser les callbacks de cycle de vie
<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>Attributs et propriétés
Les éléments personnalisés peuvent avoir des attributs et des propriétés pour gérer leur état et leur comportement. Les attributs sont définis directement en HTML et sont toujours des string, tandis que les propriétés sont définies sur l'objet DOM de l'élément et peuvent être de n'importe quel type de données.
Le point clé est attributeChangedCallback : il ne se déclenche que pour les attributs explicitement listés dans le getter static get observedAttributes() de l'élément. Si un attribut ne figure pas dans ce tableau, sa modification ne déclenche aucun callback. Une convention courante consiste à exposer une propriété getter/setter qui se reflète simplement dans un attribut, afin que le code JavaScript et le HTML restent synchronisés.
Exemple : gérer les attributs et les propriétés
<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>Ici, attribute-element met à jour son contenu en fonction de l'attribut data-content. La propriété content offre un moyen pratique de récupérer et de définir cet attribut par programmation.
Étendre les éléments natifs
Les éléments natifs personnalisés étendent une classe native spécifique et s'utilisent avec l'attribut is. Le grand avantage est qu'ils héritent de la sémantique et de l'accessibilité de l'élément hôte — un <button is="fancy-button"> reste un vrai bouton pour le clavier et les lecteurs d'écran.
Exemple : étendre un élément natif
<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>Ici, fancy-button étend l'élément standard <button>, en ajoutant un message d'alerte lorsque le bouton est cliqué. Le troisième argument de customElements.define — { extends: 'button' } — indique au navigateur à quelle balise cet élément personnalisé s'applique.
Safari ne prend pas en charge les éléments natifs personnalisés (la forme is=). Pour une compatibilité étendue, préférez les éléments personnalisés autonomes et réimplémentez l'accessibilité nécessaire, ou chargez un polyfill.
Bonnes pratiques pour les éléments personnalisés
- Utilisez le Shadow DOM : encapsulez toujours la structure interne et les styles de votre élément personnalisé à l'aide du Shadow DOM.
- Définissez des API claires : fournissez des API claires et intuitives pour vos éléments personnalisés grâce à des attributs et des propriétés bien documentés.
- Gestion du cycle de vie : gérez correctement les callbacks de cycle de vie de l'élément pour garantir un comportement robuste et éviter les fuites mémoire.
- Accessibilité : assurez-vous que vos éléments personnalisés sont accessibles en incluant les rôles et propriétés ARIA appropriés.
- Tests : testez minutieusement vos éléments personnalisés sur différents navigateurs et environnements pour garantir la compatibilité et la stabilité.
Conclusion
Les éléments personnalisés offrent un moyen puissant d'étendre HTML, permettant la création de composants réutilisables et encapsulés avec un comportement personnalisé. En tirant parti des fonctionnalités des éléments personnalisés — notamment les callbacks de cycle de vie, les attributs, les propriétés et le Shadow DOM — les développeurs peuvent créer des applications web sophistiquées et maintenables.
Commencez dès aujourd'hui à expérimenter les éléments personnalisés dans vos projets et découvrez de nouvelles possibilités pour le développement web. Les exemples fournis ici ne sont qu'un point de départ — utilisez-les comme base pour créer vos propres éléments personnalisés innovants.
Sujets connexes
- Web Components — la norme générale à laquelle appartiennent les éléments personnalisés.
- Shadow DOM — encapsuler le DOM interne et les styles d'un élément.
- Shadow DOM Styling — styliser l'intérieur de votre composant.
- Shadow DOM Slots and Composition — permettre aux utilisateurs de projeter du contenu dans votre élément.
- The
<template>Element — cloner efficacement du balisage pour la racine shadow de votre élément. - Class Basic Syntax — les fonctionnalités de classe sur lesquelles reposent les éléments personnalisés.