Bouillonnement et capture d'événements JavaScript
Le bouillonnement et la capture sont deux phases du modèle de propagation d'événements qui se produit dans le DOM lors du déclenchement d'événements.
Maîtriser le bouillonnement et la capture d'événements en JavaScript
Lorsque vous cliquez sur un bouton, le clic ne se déclenche pas uniquement sur ce bouton — il traverse tous les éléments ancêtres sur le chemin aller et retour vers votre cible. Ce déplacement s'appelle la propagation d'événements, et il se produit dans deux directions : la capture (vers le bas, en direction de la cible) et le bouillonnement (vers le haut, jusqu'à la racine). Comprendre ces deux mécanismes est essentiel pour gérer les événements de manière fiable dans de vraies applications, notamment lorsque des éléments imbriqués ont leurs propres gestionnaires.
Ce guide explique le modèle de propagation, montre comment écouter chaque phase, et présente les outils pratiques — event.target, event.currentTarget, stopPropagation() et la délégation d'événements — qui rendent ces concepts utiles au quotidien. Il s'appuie sur les bases couvertes dans Introduction aux événements du navigateur et Événements JavaScript.
Comprendre la propagation d'événements
La propagation d'événements dans le DOM se déroule en trois phases, dans cet ordre exact :
- Phase de capture — l'événement part du sommet de l'arbre (
window→document→<html>→ …) et descend vers l'élément cible. - Phase cible — l'événement atteint l'élément avec lequel vous avez réellement interagi.
- Phase de bouillonnement — l'événement remonte depuis la cible jusqu'à la racine.
│ capturing (down) ▲ bubbling (up)
<html> ▼ │
<div> ▼ │
<p> ───► target (you clicked here)Par défaut, les gestionnaires ajoutés avec addEventListener et les attributs inline on* s'exécutent pendant la phase de bouillonnement. La phase de capture nécessite une activation explicite.
Bouillonnement d'événements
Pendant la phase de bouillonnement, un événement part de l'élément le plus spécifique (le nœud le plus profond avec lequel vous avez interagi) et remonte à travers chaque ancêtre vers le document. C'est le comportement par défaut pour presque tous les événements.
<div onclick="alert('You clicked the DIV!');">
Click me or one of my children:
<p onclick="alert('You clicked the P!');">Click me!</p>
</div>Si vous cliquez sur le <p>, vous verrez d'abord l'alerte pour <p>, puis l'alerte pour <div> lorsque l'événement remonte. Si vous cliquez directement sur le <div> (en dehors du <p>), seule l'alerte du <div> se déclenche — l'événement n'atteint jamais <p> car <p> n'est pas un ancêtre du point de clic.
Capture d'événements
La capture est la première phase, où l'événement descend vers la cible. Elle est bien moins utilisée que le bouillonnement, mais elle est pratique lorsque vous avez besoin d'intercepter un événement avant qu'un gestionnaire interne puisse s'exécuter.
Pour écouter pendant la phase de capture, passez true comme troisième argument de addEventListener (ou utilisez { capture: true }) :
<div id="outer">
Click me or one of my children:
<p id="inner">Click me!</p>
</div>
<script>
document.getElementById('outer').addEventListener('click', function () {
alert('Captured on DIV!');
}, true); // true → capturing phase
document.getElementById('inner').addEventListener('click', function () {
alert('Captured on P!');
}, true);
</script>Cliquez sur le <p> et les alertes se déclenchent de haut en bas : DIV en premier (un ancêtre atteint pendant la descente), puis P (la cible). Avec des gestionnaires de bouillonnement, l'ordre serait inversé.
Cibler le bon élément
À l'intérieur d'un gestionnaire, vous devez généralement savoir sur quel élément l'événement a démarré, par rapport à l'élément auquel le gestionnaire est attaché. Deux propriétés répondent à cette question :
event.target— l'élément où l'événement a pris naissance (le plus profond sur lequel on a cliqué). Il reste le même tout au long de la propagation.event.currentTarget— l'élément dont l'écouteur est en cours d'exécution. Il change au fur et à mesure que l'événement traverse l'arbre, et il est égal àthisdans un gestionnaire de fonction classique.
function logTargets(event) {
console.log("target:", event.target.tagName);
console.log("currentTarget:", event.currentTarget.tagName);
}
// Imagine this handler is on a <div> and you click a nested <p>:
// target: P (where the click happened)
// currentTarget: DIV (where the listener lives)event.target est ce qui rend possible la délégation d'événements (présentée ci-dessous) — un seul gestionnaire sur un parent peut identifier exactement quel enfant a été cliqué.
Contrôler la propagation
JavaScript fournit plusieurs méthodes pour contrôler la distance parcourue par un événement.
| Méthode | Effet |
|---|---|
event.stopPropagation() | Empêche l'événement de continuer vers l'élément suivant dans le chemin (plus de bouillonnement/capture). Les gestionnaires sur le même élément s'exécutent quand même. |
event.stopImmediatePropagation() | Arrête la propagation et empêche tout autre gestionnaire sur le même élément de s'exécuter. |
event.preventDefault() | Annule l'action par défaut du navigateur (par ex. suivre un lien). N'arrête pas la propagation. |
stopPropagation()etpreventDefault()sont indépendants. Arrêter la propagation n'annule pas le comportement par défaut, et inversement.
Note sur les événements qui ne bouillonnent pas
La plupart des événements bouillonnent, mais certains ne le font pas — par exemple focus, blur, mouseenter, mouseleave et load. Pour ceux-ci, vous ne pouvez pas compter sur un gestionnaire parent qui les intercepterait via le bouillonnement ; utilisez les équivalents bouillonnants (focusin/focusout, mouseover/mouseout) ou attachez l'écouteur directement. Vous pouvez toujours vérifier event.bubbles pour confirmer si un événement donné participe à la phase de bouillonnement.
Exemples pratiques
Exemple 1 : Arrêter le bouillonnement d'événements
Parfois, vous voulez qu'un clic sur un enfant ne déclenche pas le gestionnaire du parent :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Event Propagation Example</title>
<style>
.container {
width: 200px;
height: 200px;
background-color: lightblue;
padding: 20px;
}
.box {
width: 100px;
height: 100px;
background-color: pink;
margin-top: 20px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container" onclick="alert('You clicked the container!');">
Click the pink box to see event propagation:
<div class="box" onclick="event.stopPropagation(); alert('You clicked the box without bubbling!');"></div>
</div>
</body>
</html>Dans cet exemple, il y a un conteneur à fond bleu clair contenant une boîte rose. Cliquer n'importe où à l'intérieur du conteneur déclenche une alerte indiquant "You clicked the container!". Cependant, cliquer sur la boîte rose déclenche une alerte différente indiquant "You clicked the box without bubbling!" car event.stopPropagation() empêche l'événement de clic de remonter jusqu'au conteneur.
Exemple 2 : Utiliser à la fois le bouillonnement et la capture
Cet exemple montre comment gérer un événement à la fois dans la phase de capture et dans la phase de bouillonnement :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Event Capture and Bubbling Example</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
#outerContainer {
border: 2px solid #ccc;
padding: 20px;
margin-bottom: 20px;
background-color: #f9f9f9;
border-radius: 10px;
}
#innerElement {
background-color: #ffa8a8;
padding: 10px;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="outerContainer" onclick="alert('Event Bubbled from Outer Container');">
<p style="margin: 0;">Click anywhere in this outer container:</p>
<p id="innerElement">Click me!</p>
</div>
<script>
// Event listener attached to the outer container during the capturing phase
document.getElementById('outerContainer').addEventListener('click', function() {
alert('Event Captured by Outer Container');
}, true);
// Event listener attached to the inner element during the bubbling phase
document.getElementById('innerElement').addEventListener('click', function() {
alert('Event Bubbled from Inner Element');
}, false);
</script>
</body>
</html>Lorsque vous cliquez sur l'élément intérieur :
- L'écouteur de capture sur
#outerContainers'exécute en premier, affichant "Event Captured by Outer Container" (il se trouve sur le chemin descendant vers la cible). - L'écouteur de bouillonnement sur
#innerElements'exécute ensuite, affichant "Event Bubbled from Inner Element" (la cible elle-même). - Enfin, l'
onclickinline sur#outerContainers'exécute lors de la remontée de l'événement, affichant "Event Bubbled from Outer Container".
Cela rend le chemin de propagation complet visible dans l'ordre : capture vers le bas, atteinte de la cible, puis remontée.
Exemple 3 : Délégation d'événements
L'utilisation pratique la plus courante du bouillonnement est la délégation d'événements — attacher un seul écouteur à un parent au lieu d'un écouteur par enfant. Parce que les clics remontent, le parent peut utiliser event.target pour déterminer quel enfant a été cliqué. C'est efficace et fonctionne automatiquement pour les éléments ajoutés ultérieurement.
const list = document.getElementById("menu");
list.addEventListener("click", function (event) {
// Did the click originate on an <li>?
const item = event.target.closest("li");
if (!item || !list.contains(item)) return;
console.log("You clicked:", item.textContent);
});Avec ce seul gestionnaire, chaque <li> actuel et futur à l'intérieur de #menu est couvert — vous n'avez jamais à lier (ou relier) des écouteurs sur des éléments individuels. Voir Gestion des événements dans le DOM et Déclenchement d'événements personnalisés pour des techniques connexes.
Conclusion
La propagation d'événements déplace un événement vers le bas (capture) puis vers le haut (bouillonnement) à travers le DOM. Par défaut, les gestionnaires s'exécutent pendant le bouillonnement ; passez true (ou { capture: true }) pour écouter pendant la capture. Utilisez event.target pour trouver l'élément qui a déclenché l'événement, event.currentTarget pour l'élément qui le gère, et stopPropagation() / stopImmediatePropagation() pour limiter sa portée. Maîtrisez ces concepts et vous pourrez créer des interactions propres et efficaces — notamment grâce à la délégation d'événements — sans disperser des écouteurs sur toute votre page.