Skip to content

Event delegation

Angenommen, wir haben eine Liste mit Elementen und wir wollen auf einen Klick auf jedes dieser Elemente lauschen. Wir könnten nun jedem einzelnen Element einen EventListener zuweisen. Bei sehr vielen Items wird das aber ineffizient (man stelle sich eine Liste mit 100 oder 1000 Elementen vor …). Daher benutzen wir in solchen Fällen das event delegation pattern.

Das event delegation pattern macht sich die event propagation zunutze. Wir hängen also einen EventListener an ein übergeordnetes Element und dieses lauscht auf alle Events, die von den nachfolgenden Elementen abgefeuert werden. (Das funktioniert natürlich nur mit Elementen, die bubbeln!)

<ul>
<li class="item1">Item 1<li>
<li class="item2">Item 2<li>
<li class="item3">Item 3<li>
<li class="item4">Item 4<li>
<li class="item5">Item 5<li>
</ul>
const ul = document.querySelector('ul');
ul.addEventListener('click', e => {
// do stuff here
});

Natürlich wollen wir wissen, welches der Elemente in der Liste den Event tatsächlich abgefeuert hat. Dieses Element wird event target genannt. Wir finden es mit der Eigenschaft target.

const ul = document.querySelector('ul');
ul.addEventListener('click', e => {
console.log(e.target, e.currentTarget);
});

Ein Klick auf Item 1 ergibt:

<li class="item1"> <ul>

Ein Klick auf Item 2 ergibt:

<li class="item2"> <ul>

… und so weiter. Wir sehen also:

  • target ist das Element, das den Event abgefeuert hat
  • currentTarget ist das Element, dem wir den EventListener zugewiesen haben (und der die bubbelnden Events abfängt.)

Eine Sache, die wir beim event delegation pattern im Auge behalten müssen, ist, dass es auch auf Klicks hört, die oberhalb der event targets (also in dem Fall oberhalb der Listenelemente) passieren. Das bedeutet, dass die Liste selbst gleichzeitig event target und event currentTarget sein kann.

Um das zu verhindern, müssen wir prüfen, ob das target element mit dem Element auch wirklich ein target element ist und nicht das currentTarget. Das machen wir mit der Methode matches.

matches prüft, ob das Element mit dem gewählten Selektor übereinstimmt.

element.matches(selector); // returns true or false

Wir wollen nur einen Event abfeuern, wenn der User auf ein Listenelement klickt und nicht auf die Liste selbst:

const ul = document.querySelector('ul');
ul.addEventListener('click', e => {
if (e.target.matches('li')) {
console.log(e.target, e.currentTarget);
}
});

Angenommen, wir wollen einen EventListener an einem Button anbringen. Innerhalb dieses Buttons ist ein SVG-Icon.

<button>
<svg><!-- Icon --></svg>
Button Text
</button>
const button = document.querySelector('button');
button.addEventListener('click', e => {
// do stuff here
});

Wenn wir auf das Icon klicken, ist das SVG unser e.target. Das sollte nicht sein.
Es gibt zwei Methoden, mit denen wir sicherstellen können, dass wir immer das button Element bekommen:

  • pointer-events auf none setzen
  • closest verwenden

pointer-events ist eine CSS-Eigenschaft, die bestimmt, wie ein Element auf Mausklicks reagiert. Wenn ein Element die Eigenschaft pointer-event: none besitzt, reagiert es nicht auf Mausklicks. Wir können daher in einem Reset-Stylesheet allen Nachkommen des Button-Elements ein pointer-event: none mitgeben:

button * {
pointer-events: none;
}

closest sucht im DOM aufwärts nach einem Element, das auf den gewählten Selektor passt. Das schließt auch das Element selbst mit ein.

element.closest(selector)
  • Wenn es ein Element findet, das auf den Selektor passt, wird das Element zurückgegeben.
  • Wenn es kein Element findet, gibt es undefined zurück.

Wenn wir nach button suchen, können wir sicherstellen, dass wir mit dem Button-Element arbeiten, auch wenn der User auf das SVG geklickt hat:

const button = document.querySelector('button');
button.addEventListener('click', e => {
const button = e.target.closest('button');
if (button) {
// do stuff here when user clicks any element within 'button'
}
});
<ul class="famous">
<li class="item-1"><a href="#">Benjamin Franklin</a></li>
<li class="item-2"><a href="#">Thomas Edison</a></li>
<li class="item-3"><a href="#">Franklin Roosevelt</a></li>
<li class="item-4"><a href="#">Napoleon Bonaparte</a></li>
<li class="item-5"><a href="#">Abraham Lincoln</a></li>
</ul>
  1. Erstelle einen Event Listener, der das event delegation pattern benutzt
  2. Logge das Element, wenn target matches li
  3. Versuche beide Varianten: pointer-events und closest, um das event target zu filtern
const ul = document.querySelector('.famous');
console.log(ul);
// 1.+2.
ul.addEventListener('click', e => {
const li = e.target.closest('li');
if (e.target.matches('li')) {
console.log(e.target);
}
});
// 3. closest
ul.addEventListener('click', e => {
const li = e.target.closest('li');
if (li) {
console.log(li);
}
});
// 3. pointer-events
ul.famous {
a {
pointer-events: none;
}
}