Clean Markup

Event Delegation and DOM Event Bubbling in JavaScript

Event Delegation and DOM Event Bubbling in JavaScript

Attaching individual click event listeners to hundreds of buttons or list items inside a loop consumes unnecessary memory—and worse, any new item appended to the DOM later via AJAX or client-side rendering won't have the listener attached.

Event Delegation solves both problems by taking advantage of DOM Event Bubbling: when a user clicks a child button, that click event bubbles up through its ancestors. By attaching a single listener to the parent container and inspecting event.target.closest(), you handle current and future child elements with one function.

Example

Without Event Delegation:
- 200 list items = 200 addEventListener calls
- Dynamically added 201st item = does not respond to clicks

With Event Delegation on <ul>:
- 200 list items = 1 addEventListener call on the parent <ul>
- Dynamically added 201st item = works automatically

Input : Dynamic <ul class="tag-filter-list"> containing <button data-tag="..."> items
Output : Single delegated click listener on the parent list.

Implementation

<ul id="tag-list" class="tag-list">
  <li><button type="button" class="tag-btn" data-tag="html">HTML5</button></li>
  <li><button type="button" class="tag-btn" data-tag="css">CSS3</button></li>
  <li><button type="button" class="tag-btn" data-tag="javascript">JavaScript</button></li>
</ul>
var tagList = document.getElementById("tag-list");

if (tagList) {
  tagList.addEventListener("click", function (event) {
    // Find the closest matching button even if the user clicked a nested <span> or icon
    var button = event.target.closest(".tag-btn");

    // Ignore clicks that did not originate inside a .tag-btn inside our list
    if (!button || !tagList.contains(button)) {
      return;
    }

    var selectedTag = button.getAttribute("data-tag");
    console.log("Filtering articles by tag:", selectedTag);
  });
}

Main Point: Use event.target.closest(selector) instead of checking event.target.matches(selector) directly so clicks on nested icons or <span> elements inside a button still resolve to the button cleanly.

Credits

Photo by Florian Olivo

← Home