Event listeners are called in the order of their registration:
button.addEventListener('click', () => console.log("I run first"))
button.addEventListener('click', () => console.log("I run second"))
Sometimes you want a listener to always run first (or last), but have no control over the order in which other listeners are registered.
There is no clean mechanism in the DOM API for this. This card shows some hacks to do it anyway.
Find conflicting listeners
You can see the order of registered DOM event listeners using the function
getEventListeners(only defined in Chrome DevTools).
Exploiting the capturing phase
Before an event bubbles up from an element to the document, it travels down from the document to the element. This is called the capturing phase.
You can have a listener call in the capture phase by passing an { capture: true } option. Since other code rarely uses capturing, this effectively causes your listener to run before existing listeners:
button.addEventListener('click', () => console.log("I run second"))
button.addEventListener('click', () => console.log("I run first"), { capture: true })
Binding to an ancestor
Listeners bound to an ancestor will still observe a (bubbling) event from its descendants. Since the event bubbles up from its target to the document, you can use a delegating listener to run after most existing listeners:
document.addEventListener('click', ({ target }) => {
if (button.contains(target)) console.log("I run second")
})
button.addEventListener('click', () => console.log("I run first"))
Be mindful of memory leaks when registering listeners that way. In the example above, the listener registered to document will not be garbage collected when button is removed. You need to explicitly remove the listener when the button is detached.
Using a different but related event
In many cases you can find a different event that fires directly before or after the event you're interested in:
input.addEventListener('focus', () => console.log("I run second"))
input.addEventListener('focusin', () => console.log("I run first"))
Tip
Avoid event pairs that only go together some of the time. E.g.
mousedownfires beforeclick, butmousedowndoes not fire for keyboard users.