Skip to main content

JavaScript Events

JavaScript Event Bubbling

Written by Published

An event fired on an element travels up through its ancestors.

Click a button inside a div and the div hears about it too.

This is called bubbling, and it is what makes event delegation possible.

Example

Example

javascript

document.body.innerHTML =
  '<div id="outer"><button id="inner">Go</button></div>';

document.getElementById("outer").addEventListener("click", function () {
  console.log("outer heard it");
});

document.getElementById("inner").click();

The button was clicked, but the div's handler ran.

How an Event Travels

First it goes down from the document to the target: the capturing phase.

Then it runs on the target itself.

Then it travels back up: the bubbling phase, where handlers normally run.

Syntax

Syntax

javascript

element.addEventListener("click", handler, true);

A third argument of true listens during capturing instead.

The Order Handlers Run

Bubbling means the innermost handler runs first.

Example

Example

javascript

document.body.innerHTML =
  '<div id="outer"><div id="middle"><button id="inner">Go</button></div></div>';

const order = [];

document.getElementById("outer").addEventListener("click", function () { order.push("outer"); });
document.getElementById("middle").addEventListener("click", function () { order.push("middle"); });
document.getElementById("inner").addEventListener("click", function () { order.push("inner"); });

document.getElementById("inner").click();

console.log(order.join(" "));

The output is inner middle outer.

Capturing Runs First

A capturing listener hears the event on the way down.

Example

Example

javascript

document.body.innerHTML =
  '<div id="outer"><button id="inner">Go</button></div>';

const order = [];

document.getElementById("outer").addEventListener("click", function () {
  order.push("outer capturing");
}, true);

document.getElementById("inner").addEventListener("click", function () {
  order.push("inner");
});

document.getElementById("inner").click();

console.log(order.join(" then "));

The outer capturing handler ran before the target's own.

target Stays the Same

However far the event travels, target is still where it started.

currentTarget changes with each handler.

Example

Example

javascript

document.body.innerHTML =
  '<div id="outer"><button id="inner">Go</button></div>';

document.getElementById("outer").addEventListener("click", function (event) {
  console.log(event.target.id + " / " + event.currentTarget.id);
});

document.getElementById("inner").click();

The output is inner / outer.

Stopping the Bubble

stopPropagation prevents the event travelling any further.

Use it sparingly; it can make other handlers mysteriously stop working.

Example

Example

javascript

document.body.innerHTML =
  '<div id="outer"><button id="inner">Go</button></div>';

const order = [];

document.getElementById("outer").addEventListener("click", function () { order.push("outer"); });
document.getElementById("inner").addEventListener("click", function (event) {
  order.push("inner");
  event.stopPropagation();
});

document.getElementById("inner").click();

console.log(order.join(" "));

Only inner ran, because the event never reached the div.

Not Everything Bubbles

focus and blur do not bubble.

focusin and focusout are the bubbling versions.

Example

Example

javascript

document.body.innerHTML = '<div id="box"><input id="field"></div>';

document.getElementById("box").addEventListener("focusin", function () {
  console.log("box heard the focus");
});

document.getElementById("field").dispatchEvent(
  new Event("focusin", { bubbles: true })
);

Most events bubble, but it is worth checking when one seems not to.

Complete Example

Complete Example

html

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript Event Bubbling</title>
  <style>
    #outer { padding: 24px; background: #dbeafe; }
    #inner { padding: 12px; }
  </style>
</head>
<body>

  <h1>Event Bubbling</h1>

  <div id="outer">
    Outer
    <button id="inner">Inner button</button>
  </div>

  <p id="out"></p>

  <script>
    const out = document.getElementById("out");
    let log = "";

    document.getElementById("inner").addEventListener("click", function () {
      log = "inner";
      out.textContent = log;
    });

    document.getElementById("outer").addEventListener("click", function () {
      log = log + " then outer";
      out.textContent = log;
    });
  </script>

</body>
</html>

Try It Yourself

Run the above example in the Try It Editor.

Try stopping it:

Add event.stopPropagation() to the inner handler.

Important Points

  • Events capture down, run on the target, then bubble up.
  • Handlers run during bubbling unless you ask otherwise.
  • The innermost handler runs first.
  • target stays the same; currentTarget changes.
  • stopPropagation stops the event travelling further.

Conclusion

Bubbling explains why a parent hears about a click on its child.

It is not a quirk to work around but a feature to use.

The next lesson turns it into event delegation.