Skip to main content

Спливання подій (event bubbling)

Спливання подій (event bubbling) це механізм, за яким подія, що сталася на вкладеному елементі, передається вгору деревом DOM до його батьків. Іншими словами: коли ви клікаєте по кнопці, браузер спочатку обробляє подію на самій кнопці, а потім піднімає її до контейнера, до body і навіть до document.

Теорія

TL;DR

  • Подія виникає на конкретному елементі (event.target) і далі рухається вгору по ланцюжку предків.
  • На кожному рівні спрацьовують обробники, навішані на цьому елементі.
  • event.target це де подія сталася, event.currentTarget це чий обробник виконується зараз.
  • Спливання дає делегування подій: один слухач на батькові обслуговує будь-яку кількість дітей, зокрема доданих динамічно.
  • Зупинити рух угору можна через event.stopPropagation().
  • Спливають не всі події: focus, blur, mouseenter, mouseleave не спливають.

Швидкий приклад

html
<div id="parent"> <button id="child">Натисни мене</button> </div>
javascript
document.querySelector('#child').addEventListener('click', () => { console.log('Кнопка'); }); document.querySelector('#parent').addEventListener('click', () => { console.log('Батько'); }); document.body.addEventListener('click', () => { console.log('BODY'); });

Клік по кнопці #child виведе:

text
Кнопка Батько BODY

Спочатку спрацював обробник на елементі, де клікнули, а потім подія спливла вгору по дереву аж до body.

Механізм спливання

  1. Подія виникає на конкретному елементі, це event.target.
  2. Далі вона передається вгору: target -> його батько -> ... -> document -> window.
  3. На кожному рівні викликаються обробники, якщо вони там є.

Візуально шлях виглядає так:

text
<button> -> <div> -> <body> -> <html> -> document -> window

Кожен рівень може «почути» подію, якщо на ньому є обробник.

Формально спливання це третя фаза поширення події. Повний цикл такий: спочатку занурення (capturing) від window вниз до цілі, потім фаза цілі (target), потім спливання (bubbling) назад угору. Звичайний addEventListener(type, handler) слухає фазу спливання; щоб слухати занурення, треба передати { capture: true }.

javascript
parent.addEventListener('click', (e) => { console.log(e.target.id); // child: де клікнули console.log(e.currentTarget.id); // parent: чий обробник працює });

Навіщо потрібне спливання

1. Делегування подій (економія обробників). Можна повісити один обробник на батьківський елемент і ловити події від усіх його дітей.

html
<ul id="menu"> <li>Головна</li> <li>Каталог</li> <li>Контакти</li> </ul>
javascript
menu.addEventListener('click', (e) => { if (e.target.tagName === 'LI') { console.log('Клік по пункту:', e.target.textContent); } });

Це працює навіть для нових <li>, доданих динамічно, бо слухач висить на списку, а не на кожному пункті. Без спливання такий підхід був би неможливий.

2. Глобальні слухачі. Можна слухати події на всьому документі, наприклад щоб закрити випадне меню по кліку поза ним або залогувати активність:

javascript
document.addEventListener('click', (e) => { console.log('Клік по сторінці'); });

Зупинка спливання

Інколи не потрібно, щоб подія піднімалася далі. Тоді використовують:

javascript
event.stopPropagation();
javascript
child.addEventListener('click', (e) => { console.log('Кнопка'); e.stopPropagation(); // зупинили спливання });

Тепер у консолі буде лише:

text
Кнопка

Важливо: stopPropagation() не скасовує стандартної дії браузера, для цього є preventDefault().

Що не спливає

Деякі події не спливають, наприклад:

  • blur і focus
  • mouseenter і mouseleave

У них є аналоги, які спливають: focusin і focusout замість focus і blur, а також mouseover і mouseout замість mouseenter і mouseleave. Перевірити конкретну подію можна властивістю event.bubbles.

Підсумкова таблиця

ПоняттяОпис
Спливання подійПроцес, за якого подія піднімається від вкладеного елемента до батьків
event.targetЕлемент, де подія сталася
event.currentTargetЕлемент, чий обробник виконується зараз
event.stopPropagation()Зупиняє спливання
Використовується дляДелегування, глобальних обробників, логування

Типові помилки

  • Плутати target і currentTarget. У делегуванні перевіряти треба e.target (реальна ціль), а this і currentTarget завжди вказують на елемент зі слухачем.
  • Перевіряти лише e.target.tagName. Якщо всередині <li> є <span> чи іконка, ціллю стане саме вона. Надійніше писати e.target.closest('li').
  • Ставити stopPropagation() про всяк випадок. Це ламає делегування і глобальні слухачі на document, а помилка виявляється далеко від місця виклику.
  • Чекати спливання від focus чи mouseenter. Вони не спливають, для делегування беріть focusin або mouseover.
  • Вважати, що спливання це єдина фаза. Слухач із { capture: true } спрацює раніше за всі обробники спливання, включно з обробником на самій цілі.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.