Event Handling and Interaction APIs

1. Event Listener Registration and Removal

Method Syntax Description Browser Support
addEventListener target.addEventListener(type, listener, options) Registers event listener on target. Supports multiple listeners per event. Third parameter is options object or boolean (useCapture). All Browsers
removeEventListener target.removeEventListener(type, listener, options) Removes event listener. Must match exact function reference and options used in addEventListener. All Browsers
dispatchEvent target.dispatchEvent(event) Dispatches event to target. Returns false if event is cancelable and any handler called preventDefault(). All Browsers
Option Type Description Default
capture boolean If true, listener fires during capture phase before target phase false
once NEW boolean If true, listener automatically removed after first invocation false
passive NEW boolean If true, listener will never call preventDefault(). Improves scroll performance. false
signal NEW AbortSignal AbortSignal to remove listener when signal is aborted. Modern alternative to removeEventListener. undefined

Example: Event listener registration patterns

// Basic event listener
button.addEventListener("click", (event) => {
  console.log("Button clicked!", event);
});

// Options object - once option
button.addEventListener("click", handleClick, { "once": true });
// Automatically removed after first click

// Passive listener for scroll performance
document.addEventListener("scroll", handleScroll, { "passive": true });
// Cannot call event.preventDefault()

// Capture phase
parent.addEventListener("click", handleCapture, { "capture": true });
// Fires before child's event

// Remove event listener
const handler = (e) => console.log(e);
button.addEventListener("click", handler);
button.removeEventListener("click", handler); // Must use same function reference

// AbortController for easy removal
const controller = new AbortController();
button.addEventListener("click", handleClick, { "signal": controller.signal });
button2.addEventListener("click", handleClick2, { "signal": controller.signal });
// Remove all listeners at once
controller.abort();
Note: Use passive: true for scroll and touch events to improve performance - browser won't wait for listener to finish before scrolling. The once option is perfect for one-time events like splash screens or initial setup. AbortController provides a clean way to remove multiple listeners.

2. Event Object Properties and Methods

Property/Method Type Description Use Case
type string Event type (e.g., "click", "keydown"). Read-only. Identify event type
target EventTarget Element that originated event. Remains same through bubbling. Read-only. Get original target
currentTarget EventTarget Element with listener attached. Changes during bubbling/capture. Read-only. Get current handler element
eventPhase number Current phase: 0=NONE, 1=CAPTURING, 2=AT_TARGET, 3=BUBBLING. Read-only. Determine event phase
bubbles boolean Indicates if event bubbles up DOM tree. Read-only. Check if event bubbles
cancelable boolean Indicates if event can be canceled. Read-only. Check if cancelable
defaultPrevented boolean True if preventDefault() was called. Read-only. Check if default prevented
timeStamp number Time event was created (milliseconds since page load). Read-only. Measure timing
isTrusted boolean True if event initiated by user action, false if created by script. Read-only. Verify user interaction
preventDefault() void Cancels default browser action if event is cancelable. Does nothing if not cancelable. Prevent default behavior
stopPropagation() void Stops event from bubbling/capturing to other elements. Current element's listeners still fire. Stop event propagation
stopImmediatePropagation() void Stops propagation and prevents other listeners on current element from firing. Stop all propagation
composedPath() EventTarget[] Returns array of elements event will/did traverse. Includes shadow DOM elements. Get event path

Example: Event object properties

document.addEventListener("click", (event) => {
  // Event identification
  console.log("Type:", event.type); // "click"
  console.log("Target:", event.target); // Element clicked
  console.log("Current:", event.currentTarget); // document
  
  // Event characteristics
  console.log("Bubbles:", event.bubbles); // true
  console.log("Cancelable:", event.cancelable); // true
  console.log("Trusted:", event.isTrusted); // true (user action)
  console.log("Time:", event.timeStamp); // 1234.567
  
  // Event path
  const path = event.composedPath();
  console.log("Path:", path); // [target, parent, ..., window]
  
  // Check phase
  if (event.eventPhase === Event.AT_TARGET) {
    console.log("At target element");
  }
});

Example: Controlling event behavior

// Prevent default link navigation
link.addEventListener("click", (event) => {
  event.preventDefault();
  console.log("Navigation prevented");
});

// Stop event bubbling
child.addEventListener("click", (event) => {
  event.stopPropagation();
  console.log("Won't bubble to parent");
});

// Stop all propagation
element.addEventListener("click", (event) => {
  event.stopImmediatePropagation();
  console.log("No other listeners will fire");
});

// Check if default was prevented
form.addEventListener("submit", (event) => {
  if (!event.defaultPrevented) {
    console.log("Form will submit");
  }
});
Warning: stopPropagation() prevents parent handlers from executing, which can break event delegation patterns. Use with caution. event.target is the original element while event.currentTarget changes during propagation - common source of bugs.

3. Custom Event Creation and Dispatching

Constructor Syntax Description Use Case
Event new Event(type, options) Creates basic event. Options: bubbles, cancelable, composed. Simple custom events
CustomEvent new CustomEvent(type, options) Creates event with custom data. Adds detail property to options. Events with data payload
MouseEvent new MouseEvent(type, options) Creates mouse event with coordinates, buttons, modifiers. Includes clientX, clientY, button, etc. Simulate mouse events
KeyboardEvent new KeyboardEvent(type, options) Creates keyboard event. Options: key, code, keyCode, ctrlKey, shiftKey, altKey, metaKey. Simulate keyboard events
FocusEvent new FocusEvent(type, options) Creates focus event. Includes relatedTarget for element losing/gaining focus. Simulate focus events
InputEvent new InputEvent(type, options) Creates input event. Options: data, inputType, isComposing. For text input simulation. Simulate text input
Event Option Type Description Default
bubbles boolean Whether event bubbles up DOM tree false
cancelable boolean Whether event can be canceled with preventDefault() false
composed boolean Whether event propagates across shadow DOM boundary false
detail any Custom data for CustomEvent. Accessible via event.detail. null

Example: Creating and dispatching custom events

// Simple custom event
const simpleEvent = new Event("userLogin", {
  "bubbles": true,
  "cancelable": true
});
element.dispatchEvent(simpleEvent);

// CustomEvent with data payload
const customEvent = new CustomEvent("dataUpdate", {
  "bubbles": true,
  "detail": {
    "userId": 123,
    "timestamp": Date.now(),
    "changes": ["name", "email"]
  }
});
element.dispatchEvent(customEvent);

// Listen for custom event
element.addEventListener("dataUpdate", (event) => {
  console.log("User ID:", event.detail.userId);
  console.log("Changes:", event.detail.changes);
});

// Dispatch with return value check
const event = new CustomEvent("beforeSave", {
  "cancelable": true,
  "detail": { "data": formData }
});
const allowed = element.dispatchEvent(event);
if (!allowed) {
  console.log("Save was prevented by listener");
}

Example: Simulating native events

// Simulate mouse click
const clickEvent = new MouseEvent("click", {
  "bubbles": true,
  "cancelable": true,
  "clientX": 100,
  "clientY": 200,
  "button": 0 // Left button
});
button.dispatchEvent(clickEvent);

// Simulate keyboard event
const keyEvent = new KeyboardEvent("keydown", {
  "key": "Enter",
  "code": "Enter",
  "keyCode": 13,
  "bubbles": true,
  "cancelable": true,
  "ctrlKey": false,
  "shiftKey": false
});
input.dispatchEvent(keyEvent);

// Simulate input event
const inputEvent = new InputEvent("input", {
  "bubbles": true,
  "data": "text",
  "inputType": "insertText"
});
textarea.dispatchEvent(inputEvent);
Note: Use CustomEvent instead of Event when you need to pass data - the detail property is the standard way to attach custom data. Always set bubbles: true unless you specifically want to prevent event propagation. For component communication, custom events are cleaner than callbacks.

4. Touch and Pointer Event APIs

Event Type Description Fires When Browser Support
pointerdown Pointer becomes active (mouse, touch, pen) Button pressed, screen touched, pen contact Modern Browsers
pointerup Pointer becomes inactive Button released, touch ended, pen lifted Modern Browsers
pointermove Pointer position changes Mouse/touch/pen moves Modern Browsers
pointercancel Pointer event canceled by browser Touch interrupted, too many touches Modern Browsers
pointerenter Pointer enters element boundary Cursor enters, touch starts in element. Doesn't bubble. Modern Browsers
pointerleave Pointer leaves element boundary Cursor leaves, touch ends outside. Doesn't bubble. Modern Browsers
touchstart Touch point placed on surface Finger touches screen Mobile Browsers
touchmove Touch point moves along surface Finger drags on screen Mobile Browsers
touchend Touch point removed from surface Finger lifted from screen Mobile Browsers
touchcancel Touch point disrupted Touch interrupted by system (call, alert) Mobile Browsers
PointerEvent Property Type Description
pointerId number Unique identifier for pointer. Same pointer keeps same ID through interaction.
pointerType string Type of pointer: "mouse", "touch", "pen"
isPrimary boolean True if primary pointer of type (first touch, main mouse)
pressure number Pressure normalized to 0-1. 0.5 for devices without pressure.
tiltX / tiltY number Angle of pen/stylus in degrees (-90 to 90)
width / height number Contact geometry in CSS pixels
clientX / clientY number Coordinates relative to viewport
pageX / pageY number Coordinates relative to document
screenX / screenY number Coordinates relative to screen
TouchEvent Property Type Description
touches TouchList All current touches on surface
targetTouches TouchList Touches on current target element
changedTouches TouchList Touches that triggered this event

Example: Pointer events for unified input handling

// Unified pointer handling (mouse, touch, pen)
element.addEventListener("pointerdown", (event) => {
  console.log("Pointer type:", event.pointerType); // "mouse", "touch", "pen"
  console.log("Pointer ID:", event.pointerId);
  console.log("Primary:", event.isPrimary);
  console.log("Pressure:", event.pressure);
  console.log("Position:", event.clientX, event.clientY);
  
  // Capture pointer for drag operation
  element.setPointerCapture(event.pointerId);
});

element.addEventListener("pointermove", (event) => {
  if (event.pointerType === "touch") {
    // Handle touch-specific behavior
  }
});

element.addEventListener("pointerup", (event) => {
  element.releasePointerCapture(event.pointerId);
});

// Prevent touch scrolling while drawing
canvas.addEventListener("touchstart", (event) => {
  event.preventDefault();
}, { "passive": false });

Example: Multi-touch handling

// Track multiple simultaneous touches
element.addEventListener("touchstart", (event) => {
  console.log("Active touches:", event.touches.length);
  console.log("Target touches:", event.targetTouches.length);
  console.log("Changed touches:", event.changedTouches.length);
  
  // Access individual touches
  for (let touch of event.touches) {
    console.log("Touch ID:", touch.identifier);
    console.log("Position:", touch.clientX, touch.clientY);
  }
});

// Pinch-to-zoom detection
let initialDistance = 0;
element.addEventListener("touchstart", (event) => {
  if (event.touches.length === 2) {
    const touch1 = event.touches[0];
    const touch2 = event.touches[1];
    initialDistance = Math.hypot(
      touch2.clientX - touch1.clientX,
      touch2.clientY - touch1.clientY
    );
  }
});

element.addEventListener("touchmove", (event) => {
  if (event.touches.length === 2) {
    const touch1 = event.touches[0];
    const touch2 = event.touches[1];
    const distance = Math.hypot(
      touch2.clientX - touch1.clientX,
      touch2.clientY - touch1.clientY
    );
    const scale = distance / initialDistance;
    console.log("Zoom scale:", scale);
  }
});
Note: Prefer Pointer Events over touch/mouse events - they unify all input types. Use setPointerCapture() to ensure pointer events continue firing even if pointer moves outside element during drag. Set touch-action CSS property to control touch behaviors.

5. Keyboard and Mouse Event Patterns

Event Type Description Fires When Cancelable
keydown Key pressed down Key initially pressed. Repeats while held. Yes
keyup Key released Key released after press Yes
keypress DEPRECATED Character key pressed (deprecated) Use keydown instead Yes
click Element clicked Mouse down + up on same element Yes
dblclick Element double-clicked Two clicks in quick succession Yes
mousedown Mouse button pressed Button pressed on element Yes
mouseup Mouse button released Button released Yes
mousemove Mouse pointer moved Pointer moves over element Yes
mouseenter Mouse enters element Pointer enters boundary. Doesn't bubble. No
mouseleave Mouse leaves element Pointer leaves boundary. Doesn't bubble. No
mouseover Mouse enters element or child Pointer enters. Bubbles. Yes
mouseout Mouse leaves element or child Pointer leaves. Bubbles. Yes
contextmenu Context menu triggered Right-click or long-press Yes
wheel Mouse wheel scrolled Wheel rotated over element Yes
KeyboardEvent Property Type Description Example Values
key string Value of key pressed. Preferred modern property. "a", "Enter", "ArrowLeft"
code string Physical key code. Independent of keyboard layout. "KeyA", "Enter", "ArrowLeft"
keyCode DEPRECATED number Numeric key code (deprecated). Use key or code instead. 65, 13, 37
ctrlKey boolean True if Ctrl/Command key pressed during event true / false
shiftKey boolean True if Shift key pressed during event true / false
altKey boolean True if Alt/Option key pressed during event true / false
metaKey boolean True if Meta key pressed (Windows/Command key) true / false
repeat boolean True if key held down (auto-repeat) true / false
MouseEvent Property Type Description Values
button number Which button was pressed 0=Left, 1=Middle, 2=Right, 3=Back, 4=Forward
buttons number Bitmask of currently pressed buttons 1=Left, 2=Right, 4=Middle (can combine)
clientX / clientY number Coordinates relative to viewport Pixel values
pageX / pageY number Coordinates relative to document Pixel values
screenX / screenY number Coordinates relative to screen Pixel values
offsetX / offsetY number Coordinates relative to target element Pixel values
movementX / movementY number Distance moved since last mousemove Delta in pixels
relatedTarget Element Secondary target (for mouseenter/mouseleave) Element reference

Example: Keyboard event handling

// Modern key detection
document.addEventListener("keydown", (event) => {
  // Use 'key' property (preferred)
  if (event.key === "Enter") {
    console.log("Enter pressed");
  }
  
  // Use 'code' for physical key position
  if (event.code === "KeyA") {
    console.log("A key pressed (QWERTY position)");
  }
  
  // Modifier keys
  if (event.ctrlKey && event.key === "s") {
    event.preventDefault();
    console.log("Ctrl+S (Save)");
  }
  
  // Check for key combinations
  if (event.shiftKey && event.altKey && event.key === "K") {
    console.log("Shift+Alt+K pressed");
  }
  
  // Arrow keys
  switch(event.key) {
    case "ArrowUp": moveUp(); break;
    case "ArrowDown": moveDown(); break;
    case "ArrowLeft": moveLeft(); break;
    case "ArrowRight": moveRight(); break;
  }
  
  // Prevent auto-repeat
  if (event.repeat) return;
});

Example: Mouse event handling

// Mouse button detection
element.addEventListener("mousedown", (event) => {
  switch(event.button) {
    case 0: console.log("Left click"); break;
    case 1: console.log("Middle click"); break;
    case 2: console.log("Right click"); break;
  }
});

// Mouse position
element.addEventListener("click", (event) => {
  console.log("Viewport:", event.clientX, event.clientY);
  console.log("Document:", event.pageX, event.pageY);
  console.log("Element:", event.offsetX, event.offsetY);
  console.log("Screen:", event.screenX, event.screenY);
});

// Drag detection
let isDragging = false;
element.addEventListener("mousedown", () => {
  isDragging = true;
});
document.addEventListener("mousemove", (event) => {
  if (isDragging) {
    console.log("Dragging:", event.movementX, event.movementY);
  }
});
document.addEventListener("mouseup", () => {
  isDragging = false;
});

// Prevent context menu
element.addEventListener("contextmenu", (event) => {
  event.preventDefault();
  console.log("Custom context menu");
});
Note: Always use event.key instead of deprecated keyCode. Use mouseenter/mouseleave instead of mouseover/mouseout when you don't want events from child elements. For keyboard shortcuts, check modifier keys with ctrlKey, shiftKey, altKey, metaKey.

6. Event Delegation and Event Bubbling

Concept Description Benefits Use Case
Event Bubbling Events propagate from target element up through ancestors to document root Enables event delegation. Single listener handles multiple elements. Default event flow in DOM
Event Capturing Events propagate from document root down to target element (before bubbling) Intercept events before target. Use capture: true option. Special interception needs
Event Delegation Single listener on parent handles events from multiple children via bubbling Better performance. Works with dynamic content. Less memory. Lists, tables, dynamic UI
Event Target event.target is element that triggered event (child) Identify actual clicked element in delegation pattern Determine source element
Current Target event.currentTarget is element with listener attached (parent) Reference to delegating element Access parent in delegation
Event Phase Value Description Direction
NONE 0 Event not being processed N/A
CAPTURING_PHASE 1 Event traveling from root to target Top-down
AT_TARGET 2 Event reached target element At target
BUBBLING_PHASE 3 Event traveling from target to root Bottom-up

Example: Event delegation pattern

// Instead of attaching listener to each item
// ❌ Bad: Many listeners, doesn't work with dynamic content
document.querySelectorAll(".item").forEach((item) => {
  item.addEventListener("click", handleClick);
});

// ✅ Good: Single listener on parent
const list = document.querySelector("#list");
list.addEventListener("click", (event) => {
  // Check if clicked element matches selector
  if (event.target.matches(".item")) {
    console.log("Item clicked:", event.target);
    handleItem(event.target);
  }
  
  // Or use closest for nested elements
  const item = event.target.closest(".item");
  if (item && list.contains(item)) {
    console.log("Item found:", item);
  }
});

// Works automatically with dynamically added items
const newItem = document.createElement("div");
newItem.className = "item";
list.appendChild(newItem); // Click handler works immediately!

Example: Event propagation and phases

// Event flow: Capture → Target → Bubble
const parent = document.querySelector("#parent");
const child = document.querySelector("#child");

// Capture phase (top-down)
parent.addEventListener("click", (event) => {
  console.log("Parent - Capture phase");
  console.log("Phase:", event.eventPhase); // 1 (CAPTURING_PHASE)
}, { "capture": true });

// Target phase
child.addEventListener("click", (event) => {
  console.log("Child - Target phase");
  console.log("Phase:", event.eventPhase); // 2 (AT_TARGET)
  console.log("Target:", event.target); // child
  console.log("Current:", event.currentTarget); // child
});

// Bubble phase (bottom-up)
parent.addEventListener("click", (event) => {
  console.log("Parent - Bubble phase");
  console.log("Phase:", event.eventPhase); // 3 (BUBBLING_PHASE)
  console.log("Target:", event.target); // child (original)
  console.log("Current:", event.currentTarget); // parent
});

// Click child outputs:
// "Parent - Capture phase"
// "Child - Target phase"
// "Parent - Bubble phase"

Example: Advanced delegation patterns

// Multi-action delegation
document.querySelector("#toolbar").addEventListener("click", (event) => {
  const button = event.target.closest("button");
  if (!button) return;
  
  const action = button.dataset.action;
  switch(action) {
    case "save":
      handleSave();
      break;
    case "delete":
      handleDelete();
      break;
    case "edit":
      handleEdit();
      break;
  }
});

// Table row delegation
table.addEventListener("click", (event) => {
  const row = event.target.closest("tr");
  if (!row || row.parentElement.tagName === "THEAD") return;
  
  const cell = event.target.closest("td");
  const columnIndex = Array.from(row.cells).indexOf(cell);
  
  console.log("Row:", row.rowIndex);
  console.log("Column:", columnIndex);
  console.log("Data:", row.dataset.id);
});

// Form delegation
form.addEventListener("input", (event) => {
  const input = event.target;
  if (input.matches("[data-validate]")) {
    validateField(input);
  }
});

// Performance: Delegate to document for global handlers
document.addEventListener("click", (event) => {
  // Close dropdowns when clicking outside
  if (!event.target.closest(".dropdown")) {
    closeAllDropdowns();
  }
});
Note: Event delegation is essential for performance with large lists (hundreds/thousands of items). It automatically handles dynamically added elements without re-attaching listeners. Use event.target.closest(selector) to handle clicks on nested elements within delegated items.
Warning: Not all events bubble! Events like focus, blur, mouseenter, mouseleave don't bubble. Use their bubbling alternatives: focusin, focusout, mouseover, mouseout for delegation. Always check if event.target matches your selector before acting.

Event Handling Best Practices

  • Use addEventListener with options object for modern features (once, passive, signal)
  • Prefer Pointer Events over separate mouse/touch handlers for unified input
  • Use event delegation for lists and dynamic content - better performance and automatic handling of new elements
  • Set passive: true for scroll/touch events to improve performance
  • Use event.key instead of deprecated keyCode for keyboard events
  • Remember: event.target = original element, event.currentTarget = element with listener
  • Call preventDefault() to stop default browser actions, stopPropagation() to prevent bubbling
  • Use CustomEvent with detail property for component communication
  • Clean up listeners with AbortController or removeEventListener to prevent memory leaks