DEV Community


Posted on


Everything about Event Handlers and Event Propagation

This has been written over a hundred times before and probably holds more than a hundred answers on StackOverflow. But over time, going through different articles and answers, I curated the important parts and writing it down as this post.

Event Handling

There are 3 ways to attach a handler to an event in JavaScript.

  • HTML Attribute
  • DOM Property
  • addEventListener()

HTML attribute

This is the old school way of attaching a handler to an event by adding an attribute to the DOM element.

Remember, a node can only have one onclick attribute. So with this approach, only one handler can be registered to an event.

<div id="el" onclick="kaboom()"></div>
Enter fullscreen mode Exit fullscreen mode

DOM property

This is the JavaScripty way of creating a DOM node on the fly, adding an attribute to it and attaching a handler to the attribute. Since this is almost the same as the previous approach, only one handler can be registered to an event.

  el = document.querySelector("#el")
  el.onclick = clickMe();
Enter fullscreen mode Exit fullscreen mode

addEventListener() 🔥

This is the modern way of binding handlers to the event. We can bind any number of handlers with the event and all these handlers will be invoked when that event happens.

  el.addEventListener("click", (e) => {
    // Some code here that goes KABOOM!
Enter fullscreen mode Exit fullscreen mode

Event Behaviours

  • preventDefault()
  • Event Bubbling
  • Event Capturing


There are a handful or pre-defined events that have default actions associated with them. For example,

  • a link by default takes you to the link’s target.
  • if you press down arrow, page scrolls down.

In these cases, the custom handlers associated with these elements will get invoked before the default actions followed by the default actions. If you want to completely unbind this default behaviour from the element, you can call the event.preventDefault() in your handler.

Note: This has got nothing to stop the event from bubbling up the DOM.

Event Bubbling & Event Capturing

Events in DOM usually get propagated as a stack of events nested in the web browser.

Event Bubbling

Event bubbling can be better explained with an example.

Let's take a nested DOM structure like this

<div id="parent">
  <div id="child"></div>
Enter fullscreen mode Exit fullscreen mode

If there are handlers attached to both #parent and #child divs,

  • when the child is clicked, due to the bubbling nature of events, first the handler for the #child div is invoked and then the event propagates to #parent div and its handler will be invoked.

In short, child first and parent next as events bubble from bottom to top.

Event Capturing

Event capturing is more of capturing the event in the DOM and handing it over to event handlers for execution. Events are captured from top-down, meaning outermost element is captured first and propagated to the inner elements. Once the events are captured, bubbling of events start from the inner-most elements.


You can stop the bubbling of events from child to parent by calling event.stopPropagation() in the child event handler.

function childHandler(event) {
Enter fullscreen mode Exit fullscreen mode

Removing an event listener

You can remove an event listener from an element by invoking removeEventListener() on the event, which accepts the event name and handler name as arguments.

function handleClick(e) {
  // go KABOOM!

element.addEventListener("click", handleClick);
element.removeEventListener("click", handleClick);

Enter fullscreen mode Exit fullscreen mode

Note: Do not use anonymous handler method. You should define the function outside the callback and then reference it in back in the removeEventListener callback.

// this is wrong as you cannot remove this event listener
element.addEventListener("click", () => { /* go KABOOM! */ });
Enter fullscreen mode Exit fullscreen mode

That's it folks. Pl drop a comment if you think this post could be improved in any way.

Top comments (5)

ssimontis profile image
Scott Simontis

Very small quirk, but when dealing with scroll events, make sure to pass {passive: true} as the options argument after the callback method. Otherwise, the browser is waiting for your event handler to determine if it should allow the scroll to happen, or if your handler is going to halt it. 99% of the time the intention is never to prevent scrolling, so it can improve performance if you have a lot of scroll listeners. This may have already become default behavior in the newest versions of Chrome.

There's also a once option you can pass in that object which will unbind the event listener as soon as it responds to a single instance of the event.

flexdinesh profile image
Dinesh Pandiyan • Edited

This is an excellent write-up. Thanks @ruphaa.

I used to pass anon handlers to events and didn't know the implications until someone pointed it out. It's a bad practice and thanks for pointing it out in the post.

ruphaa profile image

Thanks Dinesh ☺️

Even I used to do that before, I never knew there will be an impact.

ayaanraj profile image

great write-up.

ruphaa profile image

Thanks :)

Visualizing Promises and Async/Await 🤯

async await

☝️ Check out this all-time classic DEV post