JavaScript Notes
Learn JavaScript Event Capturing (Capture Phase) with examples. What is event capturing, how it works, and when to use it. Complete guide 2026.
Introduction
Event Capturing (also called "Event Trickling") is the first phase in the DOM event propagation model. When you click a button, the event doesn't just magically appear at that button — it actually travels from the very top of the DOM tree (the window object) all the way down to the target element. This downward journey is called Event Capturing or the Capture Phase.
Think of it like a postal delivery system. When a letter (event) is sent to your apartment (target element), it first goes through the country's postal office (window), then the city office (document), then your building's mailbox (parent elements), and finally reaches your apartment door (target element). The letter travels from the outermost level down to the specific destination — that's exactly how event capturing works!
In simple terms: Event Capturing means that when an event is triggered, it first starts from the window and travels downward to the target element. This is the opposite of bubbling — top-to-bottom instead of bottom-to-top.
Most developers are familiar with Event Bubbling (bottom-to-top), but understanding Event Capturing gives you complete control over how events are handled in your application. By the end of this guide, you'll understand exactly when and why to use capturing phase in your JavaScript code.
Text Diagram – Event Capture Phase Flow
Here's how an event travels during the capture phase when you click a <button> inside nested elements:
How Event Capturing Works
When any event occurs in the browser (click, keypress, mouseover, etc.), the browser follows a three-phase propagation model:
- Capture Phase (Phase 1) — Event travels from
window→document→html→body→ ... → parent of target - Target Phase (Phase 2) — Event reaches the actual target element
- Bubbling Phase (Phase 3) — Event travels back up from target → ... →
body→html→document→window
By default, when you use addEventListener(), your event listener is registered for the Bubbling Phase. To listen during the Capture Phase, you need to explicitly set the third argument.
The Third Argument of addEventListener
element.addEventListener(eventType, handler, useCapture);The third argument can be:
true— Listen during capture phasefalse(default) — Listen during bubble phase- An options object —
{ capture: true }for capture phase
event.eventPhase Property
Every event object has an eventPhase property that tells you which phase the event is currently in:
| Value | Constant | Phase |
|---|---|---|
| 0 | Event.NONE | No event is being processed |
| 1 | Event.CAPTURING_PHASE | Capture phase |
| 2 | Event.AT_TARGET | Target phase |
| 3 | Event.BUBBLING_PHASE | Bubbling phase |
Code Examples
Example 1: Basic Event Capturing
// HTML Structure:
// <div id="outer">
// <div id="inner">
// <button id="btn">Click Me</button>
// </div>
// </div>
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");
const btn = document.getElementById("btn");
// Registering listeners in CAPTURE phase (third arg = true)
outer.addEventListener("click", function () {
console.log("Outer DIV - Capture Phase");
}, true);
inner.addEventListener("click", function () {
console.log("Inner DIV - Capture Phase");
}, true);
btn.addEventListener("click", function () {
console.log("Button - Target Phase");
}, true);
// When button is clicked:Outer DIV - Capture Phase Inner DIV - Capture Phase Button - Target Phase
Notice the order is top-to-bottom! The outermost element's listener fires first, then the inner one, and finally the target element.
Example 2: Capture vs Bubble Phase Comparison
// HTML: <div id="parent"><button id="child">Click</button></div>
const parent = document.getElementById("parent");
const child = document.getElementById("child");
// Capture phase listener (fires FIRST)
parent.addEventListener("click", function () {
console.log("Parent - CAPTURE phase");
}, true);
// Bubble phase listener (fires LAST)
parent.addEventListener("click", function () {
console.log("Parent - BUBBLE phase");
}, false);
// Target element listener
child.addEventListener("click", function () {
console.log("Child - TARGET phase");
}, false);
// When child button is clicked:Parent - CAPTURE phase Child - TARGET phase Parent - BUBBLE phase
This clearly shows the order: Capture (top-down) → Target → Bubble (bottom-up).
Example 3: Using Options Object { capture: true }
// Modern way to enable capture phase
const container = document.getElementById("container");
const button = document.getElementById("btn");
// Using options object (recommended modern approach)
container.addEventListener("click", function (event) {
console.log("Container captured the event");
console.log("Event Phase:", event.eventPhase); // 1 = capturing
console.log("Current Target:", event.currentTarget.id);
console.log("Actual Target:", event.target.id);
}, { capture: true });
button.addEventListener("click", function (event) {
console.log("Button received the event");
console.log("Event Phase:", event.eventPhase); // 2 = at target
}, false);
// When button is clicked:Container captured the event Event Phase: 1 Current Target: container Actual Target: btn Button received the event Event Phase: 2
Example 4: Stopping Propagation During Capture Phase
// HTML: <div id="grandparent">
// <div id="parent">
// <button id="child">Click</button>
// </div>
// </div>
const grandparent = document.getElementById("grandparent");
const parent = document.getElementById("parent");
const child = document.getElementById("child");
grandparent.addEventListener("click", function (event) {
console.log("Grandparent - Capture");
event.stopPropagation(); // Stops event from going further down!
}, true);
parent.addEventListener("click", function () {
console.log("Parent - Capture"); // This will NOT fire!
}, true);
child.addEventListener("click", function () {
console.log("Child - Target"); // This will NOT fire!
}, true);
// When child is clicked:Grandparent - Capture
When you call stopPropagation() during capture phase, the event stops traveling down. The parent and child never receive the event!
Example 5: Tracking Event Phase with eventPhase
level1 → CAPTURING phase level2 → CAPTURING phase level3 → AT_TARGET phase level3 → AT_TARGET phase level1 → BUBBLING phase level2 → BUBBLING phase
Notice that at the target element (level3), both the capture and bubble listeners fire in registration order, both showing AT_TARGET phase.
Example 6: Practical Use — Intercepting Clicks Before They Reach Target
// Use case: Disable all clicks inside a container during loading
const form = document.getElementById("myForm");
let isLoading = false;
// Capture phase listener intercepts ALL clicks before they reach any child
form.addEventListener("click", function (event) {
if (isLoading) {
event.stopPropagation();
event.preventDefault();
console.log("Blocked click on:", event.target.tagName);
console.log("Form is loading, please wait...");
}
}, true); // capture = true, so this runs BEFORE any child listener
// Simulating loading state
isLoading = true;
// When any button inside form is clicked during loading:Blocked click on: BUTTON Form is loading, please wait...
This is a powerful pattern! By using capture phase, you can intercept and block events before they reach child elements.
Example 7: Event Delegation with Capture Phase
// Capture phase delegation — catch events before they reach targets
const nav = document.getElementById("navigation");
nav.addEventListener("click", function (event) {
const link = event.target.closest("a");
if (link) {
const href = link.getAttribute("href");
if (href.startsWith("http")) {
console.log("External link detected:", href);
console.log("Opening in new tab...");
event.preventDefault();
event.stopPropagation();
// window.open(href, '_blank');
} else {
console.log("Internal link:", href);
console.log("Navigating via SPA router...");
}
}
}, true); // Capture phase catches it first!
// When clicking <a href="https://example.com">External</a>:External link detected: https://example.com Opening in new tab...
Enabling Capture Phase
There are two ways to enable event capturing in JavaScript:
Method 1: Boolean Third Argument (Legacy)
// Pass true as third argument
element.addEventListener("click", handler, true);
// Equivalent to capture phase
// This is the older syntax but still works everywhere(Listener registered for capture phase)
Method 2: Options Object (Modern / Recommended)
// Pass an options object with capture: true
element.addEventListener("click", handler, { capture: true });
// You can combine with other options
element.addEventListener("click", handler, {
capture: true,
once: true, // Remove after first invocation
passive: true // Won't call preventDefault()
});
console.log("Listener added with capture + once + passive");Listener added with capture + once + passive
Important: Removing Capture Listeners
When removing a capture phase listener, you must specify capture: true (or true) again:
function handleClick(event) {
console.log("Captured!");
}
// Adding capture listener
document.addEventListener("click", handleClick, true);
// Removing — MUST match the capture flag!
document.removeEventListener("click", handleClick, true);
// OR
document.removeEventListener("click", handleClick, { capture: true });
console.log("Capture listener removed successfully");Capture listener removed successfully
Event Capturing vs Event Bubbling
Here's a detailed comparison between the two phases:
Comparison Table
| Feature | Event Capturing | Event Bubbling |
|---|---|---|
| Direction | Top → Down (window to target) | Bottom → Up (target to window) |
| Phase Number | Phase 1 | Phase 3 |
| Default in addEventListener | No (must explicitly enable) | Yes (default behavior) |
| How to Enable | { capture: true } or true | { capture: false } or false or omit |
| Fires First | ✅ Yes, always fires before bubbling | Fires after capturing |
| Use Case | Intercepting events before target | Most common event handling |
| Event Delegation | Less common | Most common approach |
| stopPropagation() | Prevents event from going down | Prevents event from going up |
| Browser Support | All modern browsers | All browsers |
| Common Usage | Security checks, global interceptors | Normal event handlers |
Side-by-Side Code Example
1. Box CAPTURE 2. Btn CAPTURE 3. Btn BUBBLE 4. Box BUBBLE
When to Use Capturing
Event capturing is less commonly used than bubbling, but there are specific scenarios where it's the right tool:
1. Intercepting Events Before They Reach Target
// Block all interactions in a disabled section
const section = document.getElementById("disabled-section");
section.addEventListener("click", function (e) {
if (section.classList.contains("disabled")) {
e.stopPropagation();
e.preventDefault();
console.log("Section is disabled — click blocked");
}
}, true); // Must be capture to intercept before children get itSection is disabled — click blocked
2. Focus Management
Focus entered form via: email
3. Analytics and Logging
Analytics: {"element":"BUTTON","id":"signup","class":"btn-primary","timestamp":1718150400000}4. Security — Preventing Unauthorized Actions
// Prevent clipboard hijacking by intercepting copy events
document.addEventListener("copy", function (e) {
const selection = window.getSelection().toString();
if (selection.includes("CONFIDENTIAL")) {
e.preventDefault();
e.stopPropagation();
console.log("Copy blocked: Confidential content detected");
}
}, true); // Capture phase = runs before any other copy handlerCopy blocked: Confidential content detected
🚫 Common Mistakes
Mistake 1: Forgetting to Specify Capture When Removing Listener
// ❌ WRONG — This will NOT remove the capture listener
function handler(e) {
console.log("Captured:", e.target.id);
}
document.addEventListener("click", handler, true); // Added with capture
document.removeEventListener("click", handler); // ❌ Defaults to bubble!
// The listener is still active!
console.log("Listener NOT removed (wrong)");Listener NOT removed (wrong)
// ✅ CORRECT — Match the capture flag when removing
function handler(e) {
console.log("Captured:", e.target.id);
}
document.addEventListener("click", handler, true);
document.removeEventListener("click", handler, true); // ✅ Matches!
console.log("Listener removed successfully (correct)");Listener removed successfully (correct)
Mistake 2: Assuming Event Phase Order at Target Element
Bubble listener Capture listener
Capture listener Bubble listener
Mistake 3: Using stopPropagation in Capture Without Realizing It Blocks Everything Below
// ❌ WRONG — Blocking ALL clicks unintentionally
document.addEventListener("click", function (e) {
console.log("Document captured click");
e.stopPropagation(); // ❌ This blocks ALL elements from receiving the click!
}, true);
document.getElementById("btn").addEventListener("click", function () {
console.log("Button clicked"); // ❌ This NEVER fires!
});
// When button is clicked:Document captured click
// ✅ CORRECT — Only block specific events conditionally
document.addEventListener("click", function (e) {
console.log("Document captured click");
// Only block if specific condition is met
if (e.target.classList.contains("blocked")) {
e.stopPropagation();
console.log("Blocked element click prevented");
}
}, true);
document.getElementById("btn").addEventListener("click", function () {
console.log("Button clicked — works fine!"); // ✅ This fires for non-blocked elements
});
// When a non-blocked button is clicked:Document captured click Button clicked — works fine!
Mistake 4: Confusing event.target with event.currentTarget in Capture Phase
// ❌ WRONG — Using event.target thinking it's the current element
const parent = document.getElementById("parent");
parent.addEventListener("click", function (e) {
// ❌ event.target is the CLICKED element, not necessarily 'parent'
console.log("Wrong: e.target.id =", e.target.id); // Could be a child!
e.target.style.border = "2px solid red"; // ❌ Might style wrong element
}, true);
// When a child button inside parent is clicked:Wrong: e.target.id = child-button
// ✅ CORRECT — Use event.currentTarget for the element with the listener
const parent = document.getElementById("parent");
parent.addEventListener("click", function (e) {
// ✅ event.currentTarget is ALWAYS the element the listener is attached to
console.log("Correct: e.currentTarget.id =", e.currentTarget.id);
console.log("Actual clicked: e.target.id =", e.target.id);
e.currentTarget.style.border = "2px solid green"; // ✅ Always styles parent
}, true);
// When a child button inside parent is clicked:Correct: e.currentTarget.id = parent Actual clicked: e.target.id = child-button
Mistake 5: Thinking All Events Have a Capture Phase
// ❌ WRONG — Assuming events created with bubbles:false still capture
// Actually, ALL events DO go through capture phase!
// But custom events without bubbles:true won't BUBBLE back up.
const parent = document.getElementById("parent");
const child = document.getElementById("child");
// This WILL fire during capture phase even for non-bubbling events
parent.addEventListener("myEvent", function () {
console.log("Parent captured custom event"); // ✅ This works!
}, true);
// This will NOT fire (no bubbling)
parent.addEventListener("myEvent", function () {
console.log("Parent bubble listener"); // ❌ Won't fire — event doesn't bubble
}, false);
const event = new Event("myEvent", { bubbles: false });
child.dispatchEvent(event);Parent captured custom event
Mistake 6: Not Understanding That Once a Capture Listener Stops Propagation, Bubble Phase Never Happens
// ❌ WRONG — Expecting bubble handlers to still fire
const wrapper = document.getElementById("wrapper");
const btn = document.getElementById("btn");
// Capture listener stops propagation
wrapper.addEventListener("click", function (e) {
console.log("Wrapper captured — stopping propagation");
e.stopPropagation();
}, true);
// These will NEVER fire
btn.addEventListener("click", function () {
console.log("Button handler"); // ❌ Never reached
});
wrapper.addEventListener("click", function () {
console.log("Wrapper bubble"); // ❌ Never reached
});
// When button is clicked:Wrapper captured — stopping propagation
🎯 Key Takeaways
- Event Capturing is Phase 1 of the DOM event propagation model — events travel from
windowdown to the target element.
- Default is Bubbling —
addEventListeneruses bubble phase by default. You must explicitly passtrueor{ capture: true }to use capturing.
- Capture fires BEFORE Bubble — For any non-target ancestor, capture listeners always fire before bubble listeners.
- At the Target element, listeners fire in registration order regardless of capture/bubble flag. Both show
eventPhase = 2(AT_TARGET).
- stopPropagation() in Capture prevents the event from traveling further down the DOM tree — the target and all elements below never receive the event.
- Use Capturing for Interception — When you need to intercept events before they reach child elements (security checks, disabling sections, analytics).
- focus and blur events don't bubble, so you MUST use capture phase to detect them on parent elements.
- Always match capture flag when removing —
removeEventListenermust have the same capture value used inaddEventListener.
- All events have a capture phase — Even events with
bubbles: falsestill travel down through capture phase; they just don't bubble back up.
- Modern syntax preferred — Use
{ capture: true }options object over booleantruefor better readability and to combine withonceandpassiveoptions.
❓ Interview Questions
Q1: What is Event Capturing in JavaScript? Explain with an example.
Answer: Event Capturing (also called Event Trickling) is the first phase of DOM event propagation where the event travels from the top of the DOM tree (window) down to the target element. During this phase, any ancestor element with a capture-phase listener will receive the event before the target element does.
document.getElementById("parent").addEventListener("click", function () {
console.log("Parent sees the event FIRST during capture");
}, true); // true = capture phase
document.getElementById("child").addEventListener("click", function () {
console.log("Child (target) sees it AFTER parent in capture");
});Parent sees the event FIRST during capture Child (target) sees it AFTER parent in capture
Q2: What are the three phases of event propagation? In what order do they occur?
Answer: The three phases are:
- Capturing Phase (Phase 1) — Event travels from
windowdown to the target's parent - Target Phase (Phase 2) — Event arrives at the actual target element
- Bubbling Phase (Phase 3) — Event travels back up from target to
window
You can check the current phase using event.eventPhase which returns 1, 2, or 3.
Body is in: CAPTURING phase
Q3: How do you enable Event Capturing in addEventListener?
Answer: There are two ways:
// Method 1: Boolean third argument
element.addEventListener("click", handler, true);
// Method 2: Options object (recommended)
element.addEventListener("click", handler, { capture: true });
// The options object also allows combining with other options:
element.addEventListener("click", handler, {
capture: true,
once: true,
passive: true
});
console.log("Both methods enable capture phase");Both methods enable capture phase
Q4: What happens when you call stopPropagation() during the capture phase?
Answer: When stopPropagation() is called during the capture phase, the event immediately stops traveling downward. The target element and all elements below the current element in the DOM tree will NOT receive the event. Additionally, the bubbling phase will not occur at all.
// grandparent → parent → child (target)
document.getElementById("parent").addEventListener("click", function (e) {
e.stopPropagation();
console.log("Parent stopped propagation during capture");
console.log("Child will never receive this event");
}, true);Parent stopped propagation during capture Child will never receive this event
Q5: At the target element, does the capture listener fire before the bubble listener?
Answer: No! At the target element (eventPhase === 2), the distinction between capture and bubble is irrelevant. Listeners fire in their registration order regardless of the capture flag. Both listeners will show eventPhase = 2 (AT_TARGET).
1: Bubble 2: Capture
Q6: Why would you use capture phase instead of bubble phase?
Answer: Use cases for capture phase:
- Intercepting events before they reach child elements (blocking clicks during loading)
- Non-bubbling events like
focus,blur,load,scroll— must use capture to detect on parents - Security/validation — prevent unauthorized actions before they reach targets
- Analytics — guaranteed logging even if children call
stopPropagation() - Global event interception — middleware-like pattern for all events
Q7: What is the difference between stopPropagation() and stopImmediatePropagation() in the capture phase?
Answer:
stopPropagation()— Stops the event from moving to the next element in the chain, but other listeners on the SAME element still fire.stopImmediatePropagation()— Stops everything: no more listeners on the current element AND no propagation to other elements.
const el = document.getElementById("parent");
el.addEventListener("click", function (e) {
console.log("First capture listener");
e.stopImmediatePropagation(); // Stops ALL remaining listeners
}, true);
el.addEventListener("click", function () {
console.log("Second capture listener"); // ❌ Never fires!
}, true);
// When child is clicked:First capture listener
📝 FAQ
Q: Is Event Capturing supported in all browsers?
A: Yes! Event Capturing is supported in all modern browsers (Chrome, Firefox, Safari, Edge) and has been part of the W3C DOM Level 2 Events specification since 2000. Even IE9+ supports it. The only browser that didn't support it was IE8 and below (which used attachEvent instead of addEventListener).
Q: Can I use both capture and bubble listeners on the same element for the same event?
A: Absolutely! You can attach both capture and bubble phase listeners to the same element. They are treated as separate registrations. For ancestor elements, the capture listener fires during the down-phase and the bubble listener fires during the up-phase.
Both listeners registered successfully
Q: Does jQuery support Event Capturing?
A: No, jQuery's .on() method only supports event bubbling. If you need capture phase behavior with jQuery, you must use the native addEventListener method directly. This is one of the limitations of jQuery's event system.
// jQuery — only bubble phase
// $("div").on("click", handler); // Always bubble
// Native JS — supports both
document.querySelector("div").addEventListener("click", handler, true); // Capture
console.log("jQuery does not support capture phase");jQuery does not support capture phase
Q: What events don't bubble but still have a capture phase?
A: Several events don't bubble but DO have a capture phase: focus, blur, load, unload, scroll, mouseenter, mouseleave, pointerenter, pointerleave. To detect these on parent elements, you MUST use capture phase.
Focus detected via capture!
Q: How does Event Capturing relate to Event Delegation?
A: Event Delegation typically uses bubbling — you attach one listener to a parent and let events bubble up from children. However, you can also do delegation with capturing. The capture-phase delegation is useful when you want to intercept events BEFORE they reach targets (like preventing clicks on disabled elements) or when working with non-bubbling events. Bubble-phase delegation is more common because it's simpler and the default behavior.
Disabled button click intercepted via capture delegation
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Event Capturing in JavaScript – Complete Guide to Capture Phase (2026).
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this JavaScript Master Course topic.
Search Terms
javascript-complete-guide, javascript master course, javascript, complete, guide, events, event, capturing
Related JavaScript Master Course Topics