Skip to content

Event propagation

Wenn ein Event abgefeuert wird, durchläuft er verschiedene Phasen:

  • die capturing phase
  • die target phase
  • die bubbling phase

Diese drei Ereignisse zusammen nennen wir event propagation.

Der Weg der capturing phase geht von oben nach unten: window, document, dann folgt jedes Element bis das event target erreicht wird.

Wir müssen einem Event Listener explizit sagen, wenn er auf die capturing phase lauschen soll. Wir geben dem Event Listener ein drittes Argument, useCapture mit. useCapture ist ein Boolscher Wert, der Event Listener sieht dann also so aus:

document.addEventListener('event-name', callback, useCapture)

Zur Veranschaulichung machen wir eine kleine Demo mit drei Boxen:

<div class="box box1">
<span>Box 1</span>
<div class="box box2">
<span>Box 2</span>
<div class="box box3">
<span>Box 3</span>
</div>
</div>
</div>

Jede Box bekommt einen Event Listener, mit dem wir die eventPhase und das currentTarget abfragen.

const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
box.addEventListener(
'click',
e => {
console.log(e.eventPhase, e.currentTarget);
},
true,
);
});
  • wenn eventPhase 1 zurückgibt, sind wir in der capturing phase
  • wenn eventPhase 2 zurückgibt, sind wir in der target phase
  • wenn eventPhase 3 zurückgibt, sind wir in der bubbling phase

Drei Boxen

Ein Klick auf Box 1 ergibt

2 <div class="box box1>

Ein Klick auf Box 2 ergibt

1 <div class="box box1">
2 <div class="box box2">

Ein Klick auf Box 3 ergibt

1 <div class="box box1">
1 <div class="box box2">
2 <div class="box box3">

Zuerst feuern alle Elemente mit dem entsprechenden EventListener im DOM oberhalb des geklickten Elements (capturing phase), dann feuert das geklickte Element selbst (target phase).

Die target phase sagt uns, dass JavaScript das Element, das den Event gefeuert hat, erreicht hat, und triggert alle Event Listener, die daran gebunden sind. Die target phase ignoriert die useCapture flag. Ob also true dabei steht oder nicht, ist egal.

const box3 = document.querySelector('.box3');
box3.addEventListener('click', e => {console.log(e.eventPhase, e.currentTarget)}, true);
box3.addEventListener('click', e => {console.log(e.eventPhase, e.currentTarget)});

Drei Boxen

Ein Klick auf Box 3 ergibt

2 <div class="box box3">
2 <div class="box box3">

Die bubbling phase geht den umgekehrten Weg: vom geklickten Element weg durch jedes Element durch bis zu document und window. Event Listener ohne useCapture flag werden in dieser Phase getriggert.

const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
box.addEventListener('click', e => {
console.log(e.eventPhase, e.currentTarget);
}
);
});

Drei Boxen

Ein Klick auf Box 1 ergibt

2 <div class="box box1>

Ein Klick auf Box 2 ergibt

2 <div class="box box2">
3 <div class="box box1">

Ein Klick auf Box 3 ergibt

2 <div class="box box3">
3 <div class="box box2">
3 <div class="box box1">

Zuerst feuert also das geklickte Element (target phase), dann DOM-aufwärts alle weiteren Elemente, die diesen EventListener zugewiesen haben (bubbling phase).

Bei Events, die bubbeln, ist die bubbles Eigenschaft true. Ein click Event bubbelt. blur und focus sind Events, die nicht bubbeln.

Wenn zwei EventListener dem gleichen Element zugewiesen sind, feuert der EventListener, der zuerst zugewiesen wurde, auch als erstes.

const button = document.querySelector('button');
button.addEventListener('click', e => console.log('First Event'));
button.addEventListener('click', e => console.log('Second Event'));

Falls ein Event nicht bubbeln soll, kann man das mit stopPropagation oder stopImmediatePropagation verhindern.

  • stopPropagation() verhindert, dass Events nach oben bubbeln
  • stopImmediatePropagation() verhindert, dass Events nach oben bubbeln und verhindert auch, dass weitere Events, die dem Element zugewiesen wurden, feuern.
const box2 = document.querySelector('.box2');
const box3 = document.querySelector('.box3');
box2.addEventListener('click', e => console.log('Box 2 was clicked!'));
box3.addEventListener('click', e => console.log('Box 3 was clicked!'));
box3.addEventListener('click', e => e.stopPropagation());

Ein Klick auf Box 2 ergibt

'Box 2 was clicked!'

Ein Klick auf Box 3 ergibt

'Box 3 was clicked!'

Events von Box 3 bubblen nicht mehr zu Box 2, weil wir stopPropagation() bei Box 3 aufgerufen haben.