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 — на обработчик.