W3docs

Délégation d'événements JavaScript

Apprenez la délégation d'événements JavaScript : bubbling, écouteur parent, event.target + closest(), attributs data-* et pièges courants.

Maîtriser la délégation d'événements en JavaScript

La délégation d'événements est une technique puissante en JavaScript pour gérer les événements de manière efficace, en particulier lorsqu'il s'agit de nombreux éléments similaires ou d'éléments ajoutés dynamiquement. Ce guide explique ce qu'est la délégation d'événements, pourquoi elle est utile, comment elle s'appuie sur le bubbling, ainsi que les patterns et les pièges à connaître pour l'utiliser de façon fiable.

Cette page couvre :

  • Ce qu'est la délégation d'événements et le mécanisme de bubbling dont elle dépend
  • Pourquoi elle économise de la mémoire et gère les éléments ajoutés dynamiquement
  • Le pattern robuste event.target + closest() (et pourquoi tagName seul est fragile)
  • La lecture de données depuis les éléments cliqués avec les attributs data-*
  • Les pièges courants, notamment les événements qui ne remontent pas
  • Quand ne pas utiliser la délégation

Comprendre la délégation d'événements

La délégation d'événements tire parti du fait que la plupart des événements remontent dans le DOM : lorsqu'un événement se déclenche sur un élément, il se déclenche ensuite sur le parent de cet élément, puis sur son grand-parent, et ainsi de suite jusqu'à document. Au lieu d'attacher un écouteur d'événement à chaque élément individuellement, vous attachez un seul écouteur à un ancêtre commun. Cet unique écouteur gère tous les événements qui remontent depuis n'importe quel descendant.

Si le bubbling est une nouveauté pour vous, lisez d'abord Bubbling et capturing — c'est le mécanisme sur lequel repose toute la technique.

Avantages de la délégation d'événements

  1. Efficacité mémoire : Réduit le nombre d'écouteurs d'événements dans votre application, ce qui peut économiser de la mémoire et améliorer les performances, surtout avec un grand nombre d'éléments.
  2. Éléments dynamiques : Gère les événements sur des éléments ajoutés dynamiquement au DOM après le chargement initial de la page.
  3. Simplicité : Simplifie la gestion des écouteurs d'événements, notamment lorsque de nombreux éléments se comportent de façon similaire.

Comment ça fonctionne

La délégation d'événements exploite la phase de bubbling. Un événement déclenché sur un enfant remonte vers ses ancêtres, où un seul écouteur le capture. Deux propriétés sont essentielles dans le gestionnaire :

  • event.target — l'élément réel avec lequel l'utilisateur a interagi (l'élément le plus profond). C'est ce que vous examinez pour décider quel élément a été cliqué.
  • event.currentTarget — l'élément auquel l'écouteur est attaché (le parent). Dans une fonction normale, c'est la même valeur que this.

Le gestionnaire lit event.target, détermine s'il appartient à un enfant pertinent, et agit en conséquence.

Exemples pratiques de délégation d'événements

Voici quelques exemples pratiques montrant comment implémenter la délégation d'événements dans des scénarios réels :

Exemple 1 : Gérer les clics sur une liste

Imaginez que vous ayez une liste d'éléments et que vous souhaitiez gérer les clics sur chaque élément sans attacher un écouteur d'événement à chaque élément de liste individuellement.

<ul id="myList">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <!-- More items can be added dynamically -->
</ul>

<script>
  document.getElementById('myList').addEventListener('click', function(event) {
    if (event.target.tagName === 'LI') {
      alert('You clicked on ' + event.target.textContent);
    }
  });
</script>

Explication :

  • L'écouteur d'événement est ajouté à l'élément <ul>.
  • Lorsqu'un élément de liste (<li>) est cliqué, l'événement remonte jusqu'à <ul>, et l'écouteur d'événement est déclenché.
  • La propriété event.target est vérifiée pour s'assurer que le clic provient bien d'un élément de liste.

Exemple 2 : Gérer les clics sur des boutons dans une interface dynamique

Dans une interface avec des boutons ajoutés dynamiquement, la délégation d'événements peut être utilisée pour gérer les clics sur les boutons de manière efficace.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Dynamic Button Feedback Example</title>
    <style>
        #feedback {
            color: blue;
            margin-top: 10px;
        }
    </style>
</head>
<body>
<div id="buttonContainer">
    <!-- Buttons can be added or removed dynamically -->
    <button>Action 1</button>
    <button>Action 2</button>
</div>
<div id="feedback"></div>

<script>
    const buttonContainer = document.getElementById('buttonContainer');
    const feedback = document.getElementById('feedback');

    buttonContainer.addEventListener('click', function(event) {
        if (event.target.tagName === 'BUTTON') {
            feedback.textContent = 'Button clicked: ' + event.target.textContent;
        }
    });
</script>
</body>
</html>

Explication :

  • Un seul écouteur d'événement click est ajouté à un élément conteneur.
  • Il vérifie si l'élément cliqué est un bouton et réagit à l'événement de clic selon le bouton cliqué.

Un pattern plus robuste : utiliser closest()

Vérifier event.target.tagName ne fonctionne que lorsque l'utilisateur clique exactement sur l'élément attendu. Mais les boutons et les éléments de liste contiennent souvent du balisage imbriqué — une icône, un <span>, un <strong>. Si l'utilisateur clique sur cet élément interne, event.target sera le <span>, pas le <button>, et une vérification tagName === 'BUTTON' échouera silencieusement.

Element.closest(selector) résout ce problème. Il remonte depuis event.target et retourne l'ancêtre le plus proche (l'élément lui-même inclus) qui correspond à un sélecteur CSS, ou null si aucun ne correspond. Cela rend la délégation résistante au contenu imbriqué.

<ul id="menu">
  <li class="menu-item"><span>Profile</span></li>
  <li class="menu-item"><span>Settings</span></li>
  <li class="menu-item"><span>Logout</span></li>
</ul>

<script>
  document.getElementById('menu').addEventListener('click', function (event) {
    // Find the .menu-item ancestor, even if a <span> was clicked.
    const item = event.target.closest('.menu-item');

    // closest() can return null (e.g. a click on padding around the items),
    // and we should ignore clicks outside this list entirely.
    if (!item || !this.contains(item)) return;

    console.log('Selected:', item.textContent.trim());
  });
</script>

La garde !item || !this.contains(item) est importante : closest() continue de remonter au-delà du conteneur, donc sans this.contains(item) vous pourriez correspondre à un élément en dehors de la liste. this ici est le <ul> (le currentTarget).

Lire des données avec les attributs data-*

Une fois que vous savez quel élément a été cliqué, vous avez généralement besoin de données le concernant — un id, un nom d'action, un index de ligne. Coder en dur la logique par élément va à l'encontre de l'objectif de la délégation. À la place, stockez les données sur chaque élément avec un attribut data-* et lisez-les depuis dataset.

<div id="toolbar">
  <button data-action="save">Save</button>
  <button data-action="delete">Delete</button>
  <button data-action="share">Share</button>
</div>

<script>
  const actions = {
    save:   () => console.log('Saving...'),
    delete: () => console.log('Deleting...'),
    share:  () => console.log('Sharing...'),
  };

  document.getElementById('toolbar').addEventListener('click', function (event) {
    const button = event.target.closest('button[data-action]');
    if (!button) return;

    const handler = actions[button.dataset.action];
    if (handler) handler();
  });
</script>

Ce pattern de "table d'actions" passe à l'échelle proprement : ajoutez un nouveau bouton avec un data-action et une entrée correspondante dans l'objet actions — pas de nouveaux écouteurs, pas de chaîne if/else.

Pièges et mises en garde

La délégation est puissante mais comporte des angles tranchants. Gardez ces points à l'esprit :

  • Tous les événements ne remontent pas. focus, blur, mouseenter et mouseleave ne remontent pas, donc la délégation ne les capture pas sur un parent. Utilisez plutôt leurs équivalents qui remontent : focusin/focusout pour le focus, et mouseover/mouseout pour le survol (puis filtrez avec event.target).
  • event.target vs event.currentTarget. target est l'origine de l'événement ; currentTarget (et this dans une fonction normale) est l'élément sur lequel l'écouteur est attaché. Les confondre est le bug de délégation le plus courant.
  • Les fonctions fléchées et this. Une fonction fléchée ne lie pas son propre this, donc this ne sera pas le conteneur. Utilisez event.currentTarget à la place si vous écrivez le gestionnaire comme une fonction fléchée.
  • La propagation arrêtée. Si un gestionnaire enfant appelle event.stopPropagation(), l'événement n'atteint jamais votre écouteur délégué. Évitez stopPropagation() sauf si vous en avez vraiment besoin.
  • Conteneurs trop larges. Attacher l'écouteur à document pour tout signifie que chaque clic exécute votre gestionnaire. Limitez l'écouteur à l'ancêtre le plus petit et le plus pertinent.

Quand l'utiliser (et quand ne pas l'utiliser)

SituationDélégation ?
Nombreux enfants similaires (liste, tableau, grille)Oui — un seul écouteur pour tous
Enfants ajoutés/supprimés dynamiquementOui — pas besoin de (re)lier
Un seul élément uniqueNon — liez directement ; c'est plus simple
Événements non-remontants (focus, blur)Non — utilisez focusin/focusout ou liez directement
Vous devez appeler preventDefault() tôtSouvent possible, mais liez directement pour les actions par défaut que vous devez contrôler précisément

Pour les événements que vous créez vous-même plutôt que ceux intégrés au navigateur, consultez Dispatching custom events.

Conclusion

La délégation d'événements est une technique essentielle pour une gestion efficace des événements en JavaScript, particulièrement utile dans les applications comportant de nombreux éléments ou du contenu dynamique. En comprenant et en utilisant la délégation d'événements, les développeurs peuvent améliorer considérablement les performances et la maintenabilité de leurs applications, les rendant plus réactives et plus faciles à gérer.

Pratique

Pratique
Quels sont les types d'événements JavaScript mentionnés sur le site w3docs ?
Quels sont les types d'événements JavaScript mentionnés sur le site w3docs ?
Pratique
Pourquoi event.target.closest('.menu-item') est-il plus fiable que la vérification de event.target.tagName pour la délégation ?
Pourquoi event.target.closest('.menu-item') est-il plus fiable que la vérification de event.target.tagName pour la délégation ?
Pratique
Quels événements ne remontent PAS, ce qui signifie que la délégation simple sur un parent ne les capturera pas ?
Quels événements ne remontent PAS, ce qui signifie que la délégation simple sur un parent ne les capturera pas ?
Was this page helpful?