Technology · JavaScript
JavaScript Events
Handle browser events, event delegation, bubbling, capturing, and preventDefault with vanilla JavaScript.
TL;DR
- 01Attach event listeners with addEventListener for flexible handling.
- 02Use event delegation to handle many items with one listener.
- 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
onceoption 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.cancelableto 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.