Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Спливання подій (event bubbling)». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Спливання подій (event bubbling) це механізм, за яким подія, що сталася на вкладеному елементі, послідовно передається вгору деревом DOM до його предків: від `target` до батька, далі до `body`, `html`, `document` і `window`.** На кожному рівні викликаються обробники, якщо вони там є. Саме спливання робить можливим делегування подій і глобальні слухачі, а зупинити його можна викликом `event.stopPropagation()`. ```javascript document.body.addEventListener('click', (e) => { console.log('клік дійшов до body з', e.target.tagName); }); ``` **Ключове:** подія не залишається на елементі, вона піднімається вгору, і будь-який предок може її почути.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Спливання подій (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 }` спрацює раніше за всі обробники спливання, включно з обробником на самій цілі.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.