All topics
DOMintermediate

Event Bubbling and Capturing

The two phases an event travels through the DOM tree — capturing down from the root, then bubbling back up from the target — and how to hook into either.

An event travels the tree in three phases: down to the target, at the target, then back up.

Event bubbling is like dropping a pebble into a pond at a specific point (the target) and watching ripples travel outward (bubble up) past every ring (ancestor element) around it — anyone stationed at any ring who's listening for ripples will feel it pass by, in order, from the center outward.

Key Concepts

1
    capturing   window -> document -> body -> div -> button
    target      button
    bubbling    button -> div -> body -> document -> window
2
Handlers listen in the bubbling phase by default.
    el.addEventListener('click', fn);          // bubbling
    el.addEventListener('click', fn, true);    // capturing
    el.addEventListener('click', fn, { capture: true });
3
Which explains the ordering people are asked to predict.
    outer.addEventListener('click', () => log('outer bubble'));
    outer.addEventListener('click', () => log('outer capture'), true);
    inner.addEventListener('click', () => log('inner'));
4
    // clicking inner: outer capture, inner, outer bubble
5
Capture runs on the way down, so the ancestor's capture handler fires before the target's.
6
target against currentTarget.
    e.target         what was actually clicked
    e.currentTarget  the element whose handler is running
targetcurrentTarget
7
On a handler attached to a list, target is the item clicked and currentTarget is the list. That distinction is what makes delegation work.
targetcurrentTarget
8
Not every event bubbles.
    bubble:      click, input, change, keydown, submit
    do not:      focus, blur, load, mouseenter, mouseleave
9
    focusin / focusout    the bubbling versions of focus / blur
    mouseover / mouseout  the bubbling versions of enter / leave
10
Trying to delegate focus fails for this reason, and focusin is the fix.
focusfocusin
11
Stopping it.
    e.stopPropagation()           no further elements
    e.stopImmediatePropagation()  also skips other handlers
                                  on this same element
12
The useful options.
    { once: true }      removes itself after firing
    { passive: true }   promises not to call preventDefault,
                        so the browser need not wait to scroll
    { signal }          remove via an AbortController
13
passive: true on scroll and touch listeners is a real performance win — without it the browser must run your handler before it can scroll, in case you cancel it.
passive: true
14
What the interviewer is probing.1. "Describe the three phases of an event." Probing: the model. Stalls: "It fires on the element." Moves up: capture from the root down, target, then bubble back up — listeners default to the bubble phase.
15
2. "How do you listen during capture?" Probing: the API. Stalls: "You cannot." Moves up: pass true or { capture: true } as the third argument to addEventListener.
16
3. "Which events do not bubble?" Probing: the exceptions. Stalls: "All events bubble." Moves up: focus, blur, load and scroll on an element, among others — focusin and focusout are the bubbling equivalents.
17
4. "Why does the order of handlers matter?" Probing: propagation. Stalls: "It does not." Moves up: a handler calling stopPropagation prevents later ones up the tree from running at all.