Technology · JavaScript

JavaScript Events

Handle browser events, event delegation, bubbling, capturing, and preventDefault with vanilla JavaScript.

TL;DR
  1. 01Attach event listeners with addEventListener for flexible handling.
  2. 02Use event delegation to handle many items with one listener.
  3. 03Control event flow with stopPropagation and preventDefault.

Basic Event Listeners

  • Attach event handlers with addEventListener for flexibility.
    const button = document.querySelector("button");
    button.addEventListener("click", (event) => {
      console.log("Button clicked");
    });
  • The event object contains details about what happened.
    button.addEventListener("click", (e) => {
      console.log(e.type);      // "click"
      console.log(e.target);    // the button element
      console.log(e.clientX);   // mouse position
    });
  • Remove listeners with removeEventListener if needed.
    const handler = (e) => console.log("clicked");
    button.addEventListener("click", handler);
    button.removeEventListener("click", handler);
  • Inline event handlers like onclick are outdated.
    // Avoid: <button onclick="handleClick()">Click</button>
    // Use addEventListener instead
  • Use the once option to fire a listener only one time.
    button.addEventListener("click", handler, { once: true });
    // Handler is automatically removed after first click

Event Bubbling and Capturing

  • Events bubble up from child to parent by default.
    div.addEventListener("click", () => console.log("div clicked"));
    button.addEventListener("click", () => console.log("button clicked"));
    // Clicking button logs both "button clicked" and "div clicked"
  • Use event.stopPropagation() to prevent bubbling to parents.
    button.addEventListener("click", (e) => {
      e.stopPropagation();
      console.log("button only");
    });
  • Add the third parameter true to listen during capture phase.
    div.addEventListener("click", handler, true);
    // Capture phase runs before bubble phase
  • Most events bubble, but check the MDN docs for specific events.
  • Use stopPropagation to prevent parent handlers from running.

Preventing Default Behavior

  • Use preventDefault to stop the browser's default action.
    form.addEventListener("submit", (e) => {
      e.preventDefault();
      // Form does not submit to server
      console.log("Form submission intercepted");
    });
  • preventDefault works on clickable links, form submissions, etc.
    link.addEventListener("click", (e) => {
      e.preventDefault();
      // Link does not navigate to href
    });
  • Check event.defaultPrevented to see if preventDefault was called.
    if (!event.defaultPrevented) {
      // Default action will occur
    }
  • Not all events can be prevented — check if the event is cancelable.
  • Use event.cancelable to verify the event supports preventDefault.
    link.addEventListener("click", (e) => {
      if (e.cancelable) {
        e.preventDefault();
      }
    });

Event Delegation

  • Attach a single listener to a parent to handle children efficiently.
    const list = document.querySelector("ul");
    list.addEventListener("click", (e) => {
      if (e.target.tagName === "LI") {
        console.log("Clicked item:", e.target.textContent);
      }
    });
  • This pattern is efficient when there are many similar elements.
    // Instead of attaching listeners to each item:
    items.forEach(item => item.addEventListener("click", handler));
    
    // Attach one listener to the container:
    container.addEventListener("click", handler);
  • Use e.target.closest() to find the closest matching ancestor.
    document.addEventListener("click", (e) => {
      const button = e.target.closest("button");
      if (button) console.log("Button clicked");
    });
  • Delegation works with dynamically added elements.

Common Events

  • Mouse events: click, dblclick, mousedown, mouseup, mousemove.
    element.addEventListener("mousemove", (e) => {
      console.log(`Mouse at ${e.clientX}, ${e.clientY}`);
    });
  • Keyboard events: keydown, keyup, keypress (deprecated).
    document.addEventListener("keydown", (e) => {
      console.log(`Key pressed: ${e.key}`);
    });
  • Form events: change, input, submit, reset, focus, blur.
    input.addEventListener("input", (e) => {
      console.log(`Current value: ${e.target.value}`);
    });
  • Window events: load, unload, scroll, resize.
    window.addEventListener("scroll", () => {
      console.log("Page scrolled");
    });

Tip: Use event delegation to attach a single listener to a container instead of many listeners on child elements for better performance.

Warning: preventDefault only works on cancelable events — always check if the event is cancelable before calling it.

JavaScript Error HandlingJavaScript Modules