Перейти к содержимому
4 / 57

Что такое всплытие (bubbling) и погружение (capturing) событий?


Полный ответ

HTML
<div id="outer">
  <div id="inner">
    <button id="btn">Click me</button>
  </div>
</div>

JavaScript
// Фазы прохождения события при клике на button:
//

// ФАЗА 1 — Погружение (capturing):
// window -> document -> html -> body -> #outer -> #inner -> #btn
//

// ФАЗА 2 — Цель (target):
// #btn (событие достигло цели)
//

// ФАЗА 3 — Всплытие (bubbling):
// #btn -> #inner -> #outer -> body -> html -> document -> window

const outer = document.getElementById("outer");
const inner = document.getElementById("inner");
const btn = document.getElementById("btn");

// По умолчанию — фаза всплытия
btn.addEventListener("click", () => console.log("btn — всплытие"));
inner.addEventListener("click", () => console.log("inner — всплытие"));
outer.addEventListener("click", () => console.log("outer — всплытие"));

// Клик по кнопке выведет:
// "btn — всплытие"
// "inner — всплытие"
// "outer — всплытие"

Подписка на фазу погружения:

JavaScript
// Третий аргумент true или { capture: true }
outer.addEventListener(
  "click",
  () => console.log("outer — погружение"),
  true
);
inner.addEventListener(
  "click",
  () => console.log("inner — погружение"),
  { capture: true }
);
btn.addEventListener("click", () => console.log("btn — цель"));

outer.addEventListener("click", () => console.log("outer — всплытие"));
inner.addEventListener("click", () => console.log("inner — всплытие"));

// Клик по кнопке выведет:
// "outer — погружение"    (фаза 1 — сверху вниз)
// "inner — погружение"    (фаза 1)
// "btn — цель"            (фаза 2)
// "inner — всплытие"      (фаза 3 — снизу вверх)
// "outer — всплытие"      (фаза 3)

Остановка распространения:

JavaScript
// stopPropagation — останавливает движение к следующим элементам
inner.addEventListener("click", (e) => {
    e.stopPropagation();
    console.log("inner поймал событие, дальше не пойдёт");
  });
outer.addEventListener("click", () => {
    console.log("outer — никогда не сработает при клике на inner");
  });

// stopImmediatePropagation — останавливает ВСЁ, даже другие обработчики на том же элементе
btn.addEventListener("click", (e) => {
    e.stopImmediatePropagation();
    console.log("первый обработчик");
  });
btn.addEventListener("click", () => {
    console.log("второй обработчик — не сработает!");
  });

event.target vs event.currentTarget:

JavaScript
// target — элемент, на котором произошло событие (кликнули)
// currentTarget — элемент, на котором висит обработчик

outer.addEventListener("click", function (e) {
    console.log("target:", e.target.id); // "btn" — куда кликнули
    console.log("currentTarget:", e.currentTarget.id); // "outer" — где обработчик
  });

Делегирование событий — главное применение всплытия:

JavaScript
// ❌ Плохо: обработчик на каждом элементе списка
const items = document.querySelectorAll(".item");
items.forEach((item) => {
    item.addEventListener("click", handleClick); // 1000 обработчиков!
  });

// ✅ Хорошо: один обработчик на родителе
const list = document.getElementById("list");
list.addEventListener("click", (e) => {
    const item = e.target.closest(".item"); // ищем ближайший .item
    if (!item) return; // клик не по элементу списка
    if (!list.contains(item)) return; // элемент не внутри нашего списка

    console.log("Кликнули на:", item.textContent);
  });

// Работает даже для динамически добавленных элементов!
list.innerHTML += '<li class="item">Новый элемент</li>';

// Обработчик подхватит его автоматически

Реальные кейсы

Закрытие попапа по клику снаружи:

JavaScript
const popup = document.getElementById("popup");

// Клик по попапу — не закрываем
popup.addEventListener("click", (e) => {
    e.stopPropagation(); // не пускаем событие наверх
  });

// Клик по document — закрываем
document.addEventListener("click", () => {
    popup.classList.add("hidden");
  });

Перехват на фазе погружения — блокировка кликов:

JavaScript
// Заблокировать все клики внутри disabled-секции
const section = document.getElementById("disabled-section");
section.addEventListener(
  "click",
  (e) => {
    e.stopPropagation();
    e.preventDefault();
    console.log("Секция отключена");
  },
  true // capturing — перехватываем ДО того, как событие дойдёт до дочерних элементов
);

События, которые НЕ всплывают:

JavaScript
// Эти события не всплывают:
// focus, blur — используй focusin, focusout (они всплывают)
// mouseenter, mouseleave — используй mouseover, mouseout (они всплывают)
// load, unload, abort, error (на элементах)

// ❌ Не сработает — blur не всплывает
form.addEventListener("blur", handler); // не поймает blur от input внутри

// ✅ focusin всплывает
form.addEventListener("focusin", handler); // поймает focus от любого input внутри

Резюме

Всплытие и погружение — механизм прохождения события через DOM-дерево. Всплытие (снизу вверх) — основа делегирования событий: один обработчик на родителе вместо тысячи на потомках. Погружение (сверху вниз) используется реже, но полезно для перехвата и блокировки. stopPropagation останавливает распространение, target указывает на источник, currentTarget — на обработчик.

Что такое всплытие (bubbling) и погружение (capturing) событий? | JScriptiser