Ad – 728Γ—90
🌐 DOM & Browser

JavaScript Events – Handling User Interactions

Events are signals the browser fires when something happens β€” a click, a key press, a page load, a network response. JavaScript's event system lets you listen for these signals and run code in response. Understanding event flow (bubbling vs capturing) and the Event object unlocks powerful, flexible interaction patterns.

⏱️ 20 min read 🎯 Intermediate πŸ“… Updated 2026

addEventListener

The modern way to register an event handler is addEventListener(type, handler, options). It can attach multiple listeners to the same element for the same event β€” unlike the older onclick property which overwrites previous handlers.

JavaScript
const btn = document.querySelector('#myBtn');

// Basic usage
btn.addEventListener('click', function(event) {
  console.log('Clicked!', event);
});

// Arrow function handler
btn.addEventListener('click', (e) => {
  console.log('Also clicked!', e.target);
});

// Named function – required for removeEventListener
function handleClick(e) {
  console.log('Named handler:', e.type);
}
btn.addEventListener('click', handleClick);

// removeEventListener – must pass same function reference
btn.removeEventListener('click', handleClick);

// Old way (avoid – overwrites previous handlers)
btn.onclick = () => console.log('Old style');

The Event Object

Every handler receives an Event object as its first argument. It carries information about what happened and methods to control the event's behaviour:

JavaScript
document.querySelector('a').addEventListener('click', function(e) {
  // --- Information properties ---
  console.log(e.type);           // "click"
  console.log(e.target);         // element that was clicked
  console.log(e.currentTarget);  // element the listener is attached to
  console.log(e.timeStamp);      // milliseconds since page load
  console.log(e.bubbles);        // true for most events

  // --- Mouse-specific ---
  console.log(e.clientX, e.clientY);  // viewport coordinates
  console.log(e.button);              // 0=left, 1=middle, 2=right

  // --- Control methods ---
  e.preventDefault();      // stop default browser action (e.g., navigate)
  e.stopPropagation();     // stop bubbling to parent elements
  e.stopImmediatePropagation(); // also stops other listeners on this element
});
πŸ’‘
target vs currentTarget

e.target is always the element the user interacted with (deepest clicked element). e.currentTarget is the element whose listener is currently running β€” equal to this inside a regular function handler.

Event Bubbling and Capturing

When an event fires on a nested element it travels through three phases:

  1. Capture phase β€” from window down to the target's parent
  2. Target phase β€” the target element itself
  3. Bubble phase β€” from the target back up to window

By default, addEventListener registers in the bubble phase. Pass true (or { capture: true }) as the third argument to listen in the capture phase instead.

JavaScript
// HTML: <div id="outer"><div id="inner">Click me</div></div>

const outer = document.getElementById('outer');
const inner = document.getElementById('inner');

// Bubble phase listeners (default)
outer.addEventListener('click', () => console.log('outer bubble'));
inner.addEventListener('click', () => console.log('inner bubble'));

// Capture phase listener
outer.addEventListener('click', () => console.log('outer capture'), true);

// Clicking #inner fires in this order:
// 1. "outer capture"   (capture, top-down)
// 2. "inner bubble"    (target phase)
// 3. "outer bubble"    (bubble, bottom-up)

// stopPropagation – prevents further bubbling
inner.addEventListener('click', (e) => {
  e.stopPropagation();
  console.log('inner – propagation stopped');
});
β–Ά Output (without stopPropagation)
outer capture
inner bubble
outer bubble

Common Event Types

CategoryEventsNotes
Mouseclick, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave, mouseover, mouseout, contextmenumouseenter/leave don't bubble; mouseover/out do
Keyboardkeydown, keyup, keypress (deprecated)Use e.key and e.code; keypress is legacy
Forminput, change, submit, focus, blur, focusin, focusout, resetinput fires on every keystroke; change fires on commit
Window/Documentload, DOMContentLoaded, beforeunload, resize, scroll, hashchange, popstateDOMContentLoaded fires before images load; load fires after all
Touchtouchstart, touchend, touchmove, touchcancelMobile; each touch has a touches list
Pointerpointerdown, pointerup, pointermove, pointercancelUnified mouse + touch + pen API

Keyboard Events in Practice

JavaScript
document.addEventListener('keydown', (e) => {
  console.log(e.key);   // "Enter", "a", "ArrowLeft", etc.
  console.log(e.code);  // "Enter", "KeyA", "ArrowLeft" (physical key)

  // Modifier keys
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault(); // prevent browser save dialog
    saveDocument();
  }

  if (e.key === 'Escape') closeModal();
  if (e.key === 'Enter')  submitForm();
});

// input event – fires on every character change
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (e) => {
  console.log('Current value:', e.target.value);
  filterResults(e.target.value);
});

// change event – fires when user commits (leaves field or presses Enter)
searchInput.addEventListener('change', (e) => {
  console.log('Committed value:', e.target.value);
});

Listener Options: once, passive, signal

JavaScript
// once: true β€” auto-removes listener after first invocation
btn.addEventListener('click', handleClick, { once: true });

// passive: true β€” tells browser handler will never call preventDefault()
// browser can start scrolling immediately (better performance)
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('touchmove', onTouchMove, { passive: true });

// signal β€” AbortController integration for clean cleanup
const controller = new AbortController();
btn.addEventListener('click', handleClick, { signal: controller.signal });
// Later: cancel all listeners registered with this signal
controller.abort();

// DOMContentLoaded β€” safe entry point (DOM ready, images may still load)
document.addEventListener('DOMContentLoaded', () => {
  console.log('DOM fully parsed');
  initApp();
});
⚠️
Passive scroll listeners matter

Non-passive scroll and touchmove listeners force the browser to wait for your JavaScript before scrolling, causing jank. Always pass { passive: true } unless you specifically need to call preventDefault().

Practical Exercise

πŸ‹οΈ Practical Exercise

Create a colour-picker keyboard shortcut panel:

  1. Listen for keydown on the document.
  2. When the user presses R, G, or B, change the document.body background to red, green, or blue respectively.
  3. When Escape is pressed, reset to white.
  4. Display the current key pressed in a <span> using the e.key value.

πŸ”₯ Challenge Exercise

Build a modal dialog that: (1) opens on button click, (2) closes when clicking the backdrop or pressing Escape, (3) traps focus inside the modal while open (Tab cycles only through modal focusable elements), and (4) restores focus to the trigger button on close. Use focusin events and querySelectorAll for focusable selectors.

Summary

πŸ“‹ Summary

  • Use addEventListener(type, handler, options) β€” never overwrite element.onclick.
  • The Event object carries type, target, currentTarget, and control methods.
  • Events bubble from child β†’ parent β†’ document by default.
  • Use e.stopPropagation() to stop bubbling; e.preventDefault() to cancel default action.
  • Capture-phase listeners run top-down before bubble-phase listeners.
  • Use { once: true } for one-shot listeners and { passive: true } for scroll performance.
  • Prefer e.key over e.keyCode (deprecated) for keyboard events.

Interview Questions

  • What is the difference between e.target and e.currentTarget?
  • Explain event bubbling. How would you stop it?
  • What is the difference between addEventListener and assigning to element.onclick?
  • When would you use { once: true } vs removeEventListener?
  • Why should scroll listeners use { passive: true }?

Frequently Asked Questions

What is the difference between mouseenter and mouseover? +

mouseenter fires only when the pointer enters the element itself β€” it does NOT bubble and does NOT fire again when moving into a child element. mouseover bubbles and fires each time the pointer enters the element or any of its descendants, which can cause repeated firing in complex layouts.

What is the difference between input and change events? +

input fires synchronously on every value change β€” each keystroke, paste, or cut. change fires only when the user commits the value by blurring the field or pressing Enter. For live validation or search-as-you-type, use input; for final submission logic, use change.

Does stopPropagation also prevent the default action? +

No. stopPropagation() only stops the event from travelling to parent elements. It has no effect on the browser's built-in default action (e.g., navigating on anchor click). To prevent the default action, call e.preventDefault() separately.

Ad – 336Γ—280