Propagation d'événement en JavaScript : capture, cible et remontée

Un événement traverse l'arbre en trois phases : la capture, la cible, la remontée. Comment lire eventPhase, arrêter la course et repérer ce qui ne remonte pas.
3 min de lecture
Believemy logo

Un clic sur un lien situé dans une carte, elle-même dans une liste, elle-même dans la page : l'événement ne concerne pas seulement le lien. Il traverse toute la lignée.

Comprendre ce trajet explique la plupart des comportements surprenants d'une interface, du menu qui se referme trop tôt au formulaire envoyé deux fois.


Définition

Le même stopPropagation, deux résultats selon la phase

Cliquez dans une boîte pour la viser, puis cochez la capture sans rien changer d'autre.

remontée
remontée
remontée
cible
En savoir plus
stopPropagation() appelé sur
Ordre réel des déclenchements
1acibleeventPhase 2
2.carteremontéeeventPhase 3
3#listeremontéeeventPhase 3
stopPropagation()
·pageremontéejamais appelé

event.target vaut a sur chaque ligne appelée. Seul event.currentTarget change : il vaut l'élément de la ligne.

3 écouteurs appelés sur 4. Après #liste la course s'arrête, et l'événement n'atteint jamais page.

Le journal est calculé, pas capté sur de vrais écouteurs : il applique les règles du DOM à quatre éléments imbriqués. La case pose la capture sur tous les ancêtres à la fois, là où du vrai code la choisit écouteur par écouteur. Chaque boîte ne porte qu'un écouteur, ce qui laisse de côté stopImmediatePropagation, dont la différence n'apparaît que lorsqu'un même élément en porte plusieurs.

Cliquez sur une boîte, puis activez la capture. L'ordre des ancêtres s'inverse, et c'est toute la différence entre les deux modes d'écoute.

La propagation est le trajet d'un événement à travers l'arbre du document. Il part de la racine, descend jusqu'à l'élément visé, puis remonte jusqu'à la racine. Chaque écouteur rencontré en chemin est appelé.

PhaseSensevent.eventPhase
CaptureDe la racine vers la cible1
CibleSur l'élément visé lui-même2
RemontéeDe la cible vers la racine3

Par défaut, addEventListener() enregistre pour la remontée. La capture demande l'option capture, et reste rare.


L'ordre d'appel

JAVASCRIPT
const liste = document.querySelector("#liste");
const lien = liste.querySelector("a");

liste.addEventListener("click", () => console.log("liste, capture"), true);
lien.addEventListener("click", () => console.log("lien"));
liste.addEventListener("click", () => console.log("liste, remontée"));

// Un clic sur le lien affiche, dans cet ordre :
// liste, capture
// lien
// liste, remontée

La remontée explique pourquoi un écouteur posé sur un conteneur se déclenche alors que le clic a visé un enfant. Ce n'est pas un défaut, c'est le mécanisme sur lequel repose la Délégation d'événement.


target et currentTarget

Deux propriétés répondent à deux questions différentes, et les confondre est l'erreur la plus fréquente du domaine.

  • event.target désigne l'élément réellement visé, souvent le plus profond de l'arbre.
  • event.currentTarget désigne l'élément sur lequel l'écouteur en cours a été posé.

Dans une fonction classique, this vaut currentTarget. Dans une Fonction fléchée, non : la propriété explicite est alors la seule voie sûre.


Arrêter la course

JAVASCRIPT
lien.addEventListener("click", (event) => {
  event.stopPropagation();   // la liste ne verra rien
  console.log("traité ici");
});

stopPropagation empêche l'événement d'aller plus loin dans l'arbre, mais laisse s'exécuter les autres écouteurs du même élément. stopImmediatePropagation coupe aussi ceux-là. Ni l'un ni l'autre n'annule l'action par défaut du navigateur, qui relève de preventDefault().

Bon à savoir

Arrêter la propagation par confort casse souvent du code écrit ailleurs : une fermeture de menu au clic extérieur, une mesure d'usage, un raccourci global. Réservez ce geste aux cas où il est vraiment nécessaire.


Questions fréquentes

Question

Tous les événements remontent-ils ?

Non. focus, blur, mouseenter et mouseleave restent sur leur cible. Les deux premiers ont des jumeaux qui remontent, focusin et focusout, prévus exactement pour cet usage. La propriété event.bubbles tranche le cas au moment de l'exécution.


Question

À quoi sert vraiment la phase de capture ?

À intervenir avant l'élément visé, donc avant tout écouteur posé plus bas. Elle sert à intercepter un geste de façon globale, par exemple pour bloquer les interactions pendant un chargement, ou pour rattraper un événement qui ne remonte pas.


Question

Peut-on savoir dans quelle phase on se trouve ?

Oui, event.eventPhase rend 1, 2 ou 3 selon la phase en cours. C'est surtout un outil de diagnostic : dans du code de production, la logique dépend rarement de la phase, et repose plutôt sur la comparaison entre target et currentTarget.

Termes connexes

Découvrez notre glossaire JavaScript

Tous les mots de JavaScript expliqués simplement : mots-clés, objets natifs, méthodes, erreurs et concepts. Définitions claires et exemples qui tournent, pour apprendre et pour se dépanner.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.