La clé cachée des interactions Web dynamiques
Comprendre le bouillonnement d'événements en JavaScript
Au début de mon parcours dans le développement web, le concept de bouillonnement d'événements m'a paru à la fois surprenant et captivant. Il a fallu un certain temps pour que cela devienne clair, mais une fois qu'on comprend le fonctionnement du bouillonnement d'événements, on réalise à quel point il est logique. En tant que développeur web, il est impossible d'échapper au phénomène de bouillonnement d'événements. Alors, qu'est-ce que c'est exactement ?
JavaScript utilise les événements pour permettre aux utilisateurs d'interagir avec les pages web. Un événement fait référence à une action ou à une occurrence qui peut être détectée et à laquelle votre code peut réagir. Par exemple, un clic de souris, une pression sur une touche ou la soumission d'un formulaire sont tous des événements.
Pour détecter et répondre à ces événements, JavaScript utilise des écouteurs d'événements. Un écouteur d'événement est une fonction qui "écoute" ou "attend" qu'un événement spécifique se produise sur une page. Par exemple, un clic sur un bouton. Quand un écouteur d'événement détecte l'événement qu'il surveille, il exécute le code qui lui est associé. Cette capture et cette réponse aux événements constituent ce que l'on appelle la gestion des événements.
Imaginez maintenant une page web avec trois éléments : une div, un span et un bouton. Le bouton est imbriqué dans le span, qui est lui-même imbriqué dans la div. Voici une représentation de cette structure :
Si chacun de ces éléments a un écouteur d'événement qui surveille les clics et affiche un message dans la console lorsqu'on clique dessus, que se passe-t-il si vous cliquez sur le bouton ?
Pour expérimenter cela vous-même, créez un dossier, puis créez-y trois fichiers : index.html, style.css et app.js.
Dans le fichier HTML, ajoutez le code suivant :
<html lang="fr">
<head>
<title>Bouillonnement d'événements</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>
<span><button>Cliquez ici</button></span>
</div>
<script src="app.js"></script>
</body>
</html>
Dans le fichier CSS, ajoutez ce code pour styliser les éléments div et span :
div {
border: 2px solid black;
background-color: orange;
padding: 30px;
width: 400px;
}
span {
display: inline-block;
background-color: cyan;
height: 100px;
width: 200px;
margin: 10px;
padding: 20px;
border: 2px solid black;
}
Dans le fichier JavaScript, ajoutez le code ci-dessous. Ce code ajoute des écouteurs d'événements aux éléments div, span et button. Chaque écouteur surveille l'événement "click".
const div = document.querySelector('div');
div.addEventListener('click', () => {
console.log("Vous avez cliqué sur un élément div");
});
const span = document.querySelector('span');
span.addEventListener('click', () => {
console.log("Vous avez cliqué sur un élément span");
});
const button = document.querySelector('button');
button.addEventListener('click', () => {
console.log("Vous avez cliqué sur un bouton");
});
Ouvrez maintenant le fichier HTML dans un navigateur. Inspectez la page, puis cliquez sur le bouton. Que constatez-vous ? Le résultat d'un clic sur le bouton s'affiche ci-dessous :

Cliquer sur le bouton déclenche l'écouteur d'événement qui surveille les clics sur le bouton. Cependant, les écouteurs d'événements sur les éléments span et div sont également déclenchés. Pourquoi cela ?
Lorsque vous cliquez sur le bouton, cela active l'écouteur d'événement attaché au bouton, ce qui affiche le message dans la console. Étant donné que le bouton est contenu dans un élément span, cliquer sur le bouton signifie également que nous cliquons sur l'élément span, et donc, son écouteur d'événement est aussi déclenché.
De même, l'élément span étant contenu dans l'élément div, cliquer sur le span signifie également que l'élément div est cliqué. L'écouteur d'événement du div est donc aussi déclenché. C'est cela, le bouillonnement d'événements.
Le bouillonnement d'événements
Le bouillonnement d'événements est le mécanisme par lequel un événement, déclenché dans une série d'éléments HTML imbriqués, se propage ou "remonte" à partir de l'élément le plus interne où il s'est produit vers l'élément racine. Au cours de ce processus, tous les écouteurs d'événements qui sont définis pour l'événement en question sont déclenchés.
Les écouteurs d'événements sont activés dans un ordre précis qui correspond à la façon dont l'événement se propage ou "remonte" dans l'arborescence du DOM. Considérez l'arborescence du DOM illustrée ci-dessous, qui représente la structure du HTML que nous avons utilisé dans cet article.
Un événement de clic qui remonte dans l'arborescence du DOM
L'arborescence du DOM montre un bouton imbriqué dans un span, lui-même imbriqué dans une div, puis le body et enfin l'élément HTML. Les éléments étant imbriqués, lorsque vous cliquez sur le bouton, l'événement "click" déclenchera l'écouteur d'événement attaché au bouton.
L'événement remonte ensuite dans l'arborescence du DOM, de l'élément bouton vers le span, puis vers la div, ensuite vers le body et finalement vers l'élément HTML, activant tous les écouteurs d'événements qui sont configurés pour surveiller l'événement "click" dans ces éléments, et ce, dans cet ordre.
C'est pour cela que les écouteurs d'événements attachés aux éléments span et div sont exécutés. Si nous avions également des écouteurs d'événements qui surveillaient les clics sur le body ou l'élément HTML, ils auraient également été déclenchés.
Le nœud du DOM où l'événement se produit s'appelle la cible. Dans notre cas, étant donné que le clic a lieu sur le bouton, cet élément est la cible de l'événement.
Comment arrêter le bouillonnement d'événements
Pour empêcher un événement de se propager dans le DOM, nous utilisons la méthode stopPropagation(), qui est disponible sur l'objet événement. Voici un exemple de code, que nous avons utilisé pour ajouter un écouteur d'événement à l'élément bouton :
const button = document.querySelector('button');
button.addEventListener('click', () => {
console.log("Vous avez cliqué sur un bouton");
});
Ce code provoque le bouillonnement d'événements dans l'arborescence du DOM quand un utilisateur clique sur le bouton. Pour arrêter la propagation de l'événement, nous appelons la méthode stopPropagation(), comme ceci :
const button = document.querySelector('button');
button.addEventListener('click', (e) => {
console.log("Vous avez cliqué sur un bouton");
e.stopPropagation();
});
Un gestionnaire d'événements est la fonction qui est exécutée quand un bouton est cliqué. Un écouteur d'événement transmet automatiquement un objet événement au gestionnaire d'événements. Dans notre exemple, l'objet événement est représenté par la variable e, qui est passée en paramètre au gestionnaire.
Cet objet événement, e, contient des informations sur l'événement et nous donne également accès à un ensemble de propriétés et de méthodes liées aux événements. Une de ces méthodes est stopPropagation(), qui est utilisée pour empêcher la propagation de l'événement. En appelant stopPropagation() dans l'écouteur d'événement du bouton, on empêche l'événement de remonter dans l'arborescence du DOM à partir de l'élément bouton.
Le résultat du clic sur le bouton, après avoir ajouté la méthode stopPropagation(), est affiché ci-dessous :

Nous utilisons stopPropagation() pour empêcher un événement de remonter au-delà de l'élément sur lequel nous l'appliquons. Par exemple, si nous voulions que l'événement "click" remonte de l'élément button jusqu'à l'élément span, mais pas plus haut dans l'arborescence du DOM, nous utiliserions stopPropagation() sur l'écouteur d'événement du span.
La capture d'événements
La capture d'événements est l'inverse du bouillonnement d'événements. Avec la capture d'événements, l'événement se propage de l'élément le plus externe à l'élément cible, comme illustré ci-dessous :
Un événement "click" qui se propage vers l'élément cible en utilisant la capture d'événements.
Par exemple, dans notre cas, quand vous cliquez sur l'élément bouton, si la capture d'événements est active, les écouteurs d'événements de l'élément div seront les premiers à être déclenchés. Les écouteurs du span seront ensuite déclenchés, et enfin, les écouteurs de l'élément cible seront activés.
Cependant, le bouillonnement d'événements est le mode de propagation d'événements par défaut dans le DOM. Pour changer le comportement par défaut du bouillonnement d'événements et activer la capture d'événements, il faut passer un troisième argument, avec la valeur true, à l'écouteur d'événements. Si vous ne passez pas de troisième argument, la capture d'événements est définie sur false.
Voici un exemple d'écouteur d'événements :
div.addEventListener('click', () => {
console.log("Vous avez cliqué sur un élément div");
});
Comme il n'y a pas de troisième argument, la capture d'événements est définie sur false. Pour activer la capture d'événements, nous ajoutons un troisième argument avec la valeur booléenne true.
div.addEventListener('click', () => {
console.log("Vous avez cliqué sur un élément div");
}, true);
Alternativement, vous pouvez aussi passer un objet qui définit la capture sur true, comme ceci :
div.addEventListener('click', () => {
console.log("Vous avez cliqué sur un élément div");
}, {capture: true});
Pour tester la capture d'événements, ajoutez un troisième argument à tous vos écouteurs d'événements dans votre fichier JavaScript, comme montré ici :
const div = document.querySelector('div');
div.addEventListener('click', () => {
console.log("Vous avez cliqué sur un élément div");
}, true);
const span = document.querySelector('span');
span.addEventListener('click', (e) => {
console.log("Vous avez cliqué sur un élément span");
}, true);
const button = document.querySelector('button');
button.addEventListener('click', () => {
console.log("Vous avez cliqué sur un bouton");
}, true);
Ouvrez maintenant votre navigateur et cliquez sur l'élément bouton. Vous devriez voir le résultat suivant :

Remarquez que contrairement au bouillonnement d'événements, où le message du bouton était affiché en premier, ici, la première ligne de texte affichée provient de l'élément le plus externe, le div.
Le bouillonnement d'événements et la capture d'événements sont les deux principales façons dont les événements se propagent dans le DOM. Cependant, le bouillonnement d'événements est le mode de propagation le plus couramment utilisé.
La délégation d'événements
La délégation d'événements est un modèle de conception qui permet d'attacher un seul écouteur d'événement à un élément parent commun, par exemple, un élément <ul>, au lieu d'avoir des écouteurs d'événements individuels sur chaque enfant. Les événements qui se produisent sur les éléments enfants "remontent" vers l'élément parent, où ils sont gérés par le gestionnaire d'événements défini pour l'élément parent.
Dans un cas où l'on a un élément parent qui contient des éléments enfants, on ajoute seulement un écouteur d'événements à l'élément parent. Ce gestionnaire d'événements prend en charge tous les événements qui surviennent sur les éléments enfants.
Vous vous demandez peut-être comment l'élément parent sait sur quel élément enfant on a cliqué. Comme mentionné plus tôt, un écouteur d'événements passe un objet événement au gestionnaire d'événements. Cet objet événement a des méthodes et des propriétés qui contiennent des informations sur l'événement qui s'est produit. La propriété target est une de ces propriétés. La propriété target pointe vers l'élément HTML spécifique où l'événement a eu lieu.
Par exemple, si vous avez une liste non ordonnée avec des éléments de liste et que vous attachez un écouteur d'événement à l'élément <ul>, quand un événement se produit sur un élément de liste, la propriété target de l'objet événement pointera vers cet élément de liste particulier.
Pour voir la délégation d'événements en action, ajoutez ce code HTML au fichier HTML existant :
<ul>
<li>Toyota</li>
<li>Subaru</li>
<li>Honda</li>
<li>Hyundai</li>
<li>Chevrolet</li>
<li>Kia</li>
</ul>
Ajoutez ensuite le code JavaScript suivant pour utiliser la délégation d'événements. On utilise un seul écouteur d'événements sur un élément parent pour prendre en charge les événements des éléments enfants :
const ul = document.querySelector('ul');
ul.addEventListener('click', (e) => {
// élément cible
targetElement = e.target;
// afficher le contenu de l'élément cible
console.log(targetElement.textContent);
});
Ouvrez maintenant le navigateur et cliquez sur n'importe quel élément de la liste. Le contenu de l'élément doit s'afficher dans la console comme dans l'image ci-dessous :

En utilisant un seul écouteur d'événements, nous pouvons gérer les événements de tous les éléments enfants. Avoir de nombreux écouteurs d'événements sur une page peut nuire à ses performances, consommer plus de mémoire et rendre les pages plus lentes à charger.
La délégation d'événements nous aide à éviter ces problèmes en minimisant le nombre d'écouteurs d'événements que nous utilisons sur une page. La délégation d'événements repose sur le bouillonnement d'événements. On peut donc dire que le bouillonnement d'événements peut être utilisé pour optimiser les performances des pages web.
Conseils pour une gestion efficace des événements
En tant que développeur, quand vous travaillez avec des événements dans le DOM, privilégiez la délégation d'événements plutôt que d'avoir de nombreux écouteurs d'événements sur votre page.

Lorsque vous utilisez la délégation d'événements, attachez l'écouteur d'événements à l'ancêtre commun le plus proche des éléments enfants qui nécessitent une gestion d'événements. Cela aide à optimiser le bouillonnement d'événements et minimise également le chemin que l'événement doit parcourir avant d'être géré.
Quand vous gérez des événements, utilisez à votre avantage l'objet événement fourni par l'écouteur d'événements. L'objet événement contient des propriétés telles que target, qui sont très utiles pour la gestion d'événements.
Pour avoir des sites web plus performants, évitez les manipulations excessives du DOM. Les événements qui déclenchent des manipulations fréquentes du DOM peuvent nuire aux performances de votre site web.
Enfin, faites particulièrement attention à l'imbrication des écouteurs d'événements si vous travaillez avec des éléments imbriqués. Cela peut non seulement avoir des effets négatifs sur les performances, mais aussi rendre la gestion d'événements très complexe, et votre code sera difficile à maintenir.
Conclusion
Les événements sont un outil puissant en JavaScript. Le bouillonnement d'événements, la capture d'événements et la délégation d'événements sont des concepts essentiels à la gestion d'événements en JavaScript. En tant que développeur web, vous pouvez utiliser cet article pour vous familiariser avec ces concepts afin de créer des sites web et des applications plus interactifs, dynamiques et performants.