Пошук уроків, статей та іншого контенту
Навчитеся керувати поширенням подій за допомогою stopPropagation і stopImmediatePropagation.
Коли користувач натискає кнопку, подія click виникає на конкретному елементі, але може бути оброблена й іншими елементами DOM. Це відбувається завдяки поширенню подій.
Розглянемо вкладені елементи:
<div id="card">
<button id="saveButton">Зберегти</button>
</div>Якщо натиснути кнопку, подія може пройти таким шляхом:
Від window до button — фаза захоплення (capturing phase).
На самому button — фаза цілі (target phase).
Від button назад до window — фаза спливання (bubbling phase).
За замовчуванням обробники подій працюють у фазі спливання.
const card = document.querySelector("#card");
const button = document.querySelector("#saveButton");
card.addEventListener("click", () => {
console.log("Клік на картці");
});
button.addEventListener("click", () => {
console.log("Клік на кнопці");
});Після натискання кнопки в консолі буде:
Клік на кнопці
Клік на картціСпочатку обробляється кнопка — безпосередня ціль події. Потім подія «спливає» до батьківського елемента.
stopPropagation()Метод event.stopPropagation() зупиняє поширення події до інших елементів DOM.
const card = document.querySelector("#card");
const button = document.querySelector("#saveButton");
card.addEventListener("click", () => {
console.log("Клік на картці");
});
button.addEventListener("click", (event) => {
event.stopPropagation();
console.log("Клік на кнопці");
});Тепер після натискання кнопки буде виведено лише:
Клік на кнопціПодія все одно відбулася на кнопці, але не продовжила спливати до card.
stopPropagation()Метод зупиняє перехід події між елементами, але не скасовує саму подію.
Він не:
скасовує стандартну дію браузера;
видаляє обробники подій;
зупиняє інші обробники на тому самому елементі.
Наприклад, посилання після stopPropagation() усе ще може перейти на іншу сторінку. Для скасування стандартної дії використовується окремий метод preventDefault().
link.addEventListener("click", (event) => {
event.stopPropagation();
event.preventDefault();
});stopImmediatePropagation()Метод event.stopImmediatePropagation() робить більше:
Зупиняє поширення події до інших елементів.
Зупиняє виконання наступних обробників цієї самої події на поточному елементі.
Розглянемо приклад:
const button = document.querySelector("#saveButton");
button.addEventListener("click", () => {
console.log("Перший обробник");
});
button.addEventListener("click", (event) => {
event.stopImmediatePropagation();
console.log("Другий обробник");
});
button.addEventListener("click", () => {
console.log("Третій обробник");
});Після натискання буде виведено:
Перший обробник
Другий обробникТретій обробник не виконається, оскільки другий обробник зупинив подальше виконання обробників на цьому елементі.
| Метод | Зупиняє спливання | Зупиняє інші обробники на поточному елементі | Скасовує стандартну дію | |---|---:|---:|---:| | stopPropagation() | Так | Ні | Ні | | stopImmediatePropagation() | Так | Так | Ні | | preventDefault() | Ні | Ні | Так |
stopPropagation() і preventDefault() вирішують різні завдання:
element.addEventListener("click", (event) => {
// Не передавати подію батьківським елементам
event.stopPropagation();
// Не виконувати стандартну дію браузера
event.preventDefault();
});За замовчуванням обробник реєструється для фази спливання. Щоб обробник працював під час захоплення, третім аргументом addEventListener потрібно передати true або об’єкт із властивістю capture: true.
const card = document.querySelector("#card");
const button = document.querySelector("#saveButton");
card.addEventListener(
"click",
() => {
console.log("Картка: фаза захоплення");
},
{ capture: true }
);
button.addEventListener("click", () => {
console.log("Кнопка: ціль");
});
card.addEventListener("click", () => {
console.log("Картка: фаза спливання");
});Після натискання кнопки порядок буде таким:
Картка: фаза захоплення
Кнопка: ціль
Картка: фаза спливанняstopPropagation() зупиняє подальше поширення в обох напрямках.
card.addEventListener(
"click",
(event) => {
event.stopPropagation();
console.log("Захоплення на картці");
},
{ capture: true }
);
button.addEventListener("click", () => {
console.log("Цей обробник не виконається");
});Оскільки поширення зупинено на фазі захоплення, подія не дійде до кнопки.
Уявімо картку товару. Натискання на всю картку відкриває сторінку товару, а натискання на кнопку «Додати» додає товар у кошик.
<article class="product-card" id="productCard">
<h2>Навушники</h2>
<p>Бездротові навушники з шумозаглушенням.</p>
<button type="button" id="addButton">Додати в кошик</button>
</article>
<script>
const productCard = document.querySelector("#productCard");
const addButton = document.querySelector("#addButton");
productCard.addEventListener("click", () => {
console.log("Відкриваємо сторінку товару");
});
addButton.addEventListener("click", (event) => {
event.stopPropagation();
console.log("Товар додано в кошик");
});
</script>Без stopPropagation() після натискання кнопки виконалися б обидва обробники:
Товар додано в кошик
Відкриваємо сторінку товаруМетод stopPropagation() залишає дію кнопки незалежною від дії картки.
Делегування подій — це підхід, за якого один обробник додається до спільного батьківського елемента замість окремого обробника для кожного дочірнього елемента.
<ul id="tasks">
<li>
Вивчити події
<button type="button" data-action="remove">Видалити</button>
</li>
<li>
Написати приклад
<button type="button" data-action="remove">Видалити</button>
</li>
</ul>
<script>
const tasks = document.querySelector("#tasks");
tasks.addEventListener("click", (event) => {
const removeButton = event.target.closest(
'button[data-action="remove"]'
);
if (!removeButton || !tasks.contains(removeButton)) {
return;
}
removeButton.closest("li").remove();
});
</script>У цьому прикладі подія від кнопки спливає до ul, де її обробляє один спільний обробник.
stopPropagation() заважає делегуваннюЯкщо дочірній обробник зупинить поширення події, батьківський обробник делегування не отримає цю подію.
const tasks = document.querySelector("#tasks");
tasks.addEventListener("click", (event) => {
console.log("Цей обробник використовує делегування");
});
document.querySelectorAll("button").forEach((button) => {
button.addEventListener("click", (event) => {
event.stopPropagation();
});
});Тепер обробник на tasks не виконається. Тому перед використанням stopPropagation() варто перевірити, чи не покладається застосунок на делегування подій.
target і currentTargetДля розуміння поширення подій важливо розрізняти дві властивості:
event.target — елемент, на якому подія виникла;
event.currentTarget — елемент, чий обробник зараз виконується.
const card = document.querySelector("#card");
card.addEventListener("click", (event) => {
console.log("target:", event.target);
console.log("currentTarget:", event.currentTarget);
});Якщо натиснути на кнопку всередині картки:
event.target буде кнопкою;
event.currentTarget буде карткою.
Саме тому під час делегування часто перевіряють event.target або використовують closest().
stopImmediatePropagation()stopImmediatePropagation() потрібен рідше, ніж stopPropagation(). Його можна застосувати, коли потрібно безумовно припинити виконання всіх наступних обробників поточної події.
const form = document.querySelector("#orderForm");
form.addEventListener("submit", (event) => {
if (!isUserAuthorized()) {
event.preventDefault();
event.stopImmediatePropagation();
console.log("Користувач не авторизований");
}
});
function isUserAuthorized() {
return false;
}Проте цей метод може приховати логіку інших частин програми. Якщо на формі зареєстровано кілька незалежних обробників, їхнє примусове блокування може спричинити складні для пошуку помилки.
У більшості випадків краще:
використовувати умови всередині обробників;
повертати результат перевірки;
чітко розділяти відповідальність обробників;
застосовувати stopImmediatePropagation() лише тоді, коли блокування інших обробників є навмисною поведінкою.
stopPropagation() скасує дію браузераlink.addEventListener("click", (event) => {
event.stopPropagation();
});Цей код не забороняє перехід за посиланням. Для цього потрібен preventDefault():
link.addEventListener("click", (event) => {
event.preventDefault();
});stopImmediatePropagation() без потребиЦей метод зупиняє не лише батьківські обробники, а й інші обробники на поточному елементі. Через це можна випадково порушити роботу стороннього або іншого внутрішнього коду.
Спочатку варто спробувати stopPropagation() або взагалі обійтися без зупинки поширення.
Якщо обробник батьківського елемента відповідає за делегування, stopPropagation() у дочірньому елементі не дасть батьківському обробнику виконатися.
Перед зупинкою поширення перевірте всі обробники, які можуть залежати від цієї події.
button.addEventListener("click", (event) => {
saveData();
event.stopPropagation();
});У цьому прикладі подія не спливе далі, але saveData() уже буде викликано. Якщо спочатку потрібно перевірити умову, зробіть це до виконання основної логіки.
target і currentTargetПід час делегування event.target може бути вкладеним елементом, наприклад span усередині кнопки. event.currentTarget завжди вказує на елемент, де зареєстровано поточний обробник.
Для пошуку потрібної кнопки зручно використовувати:
const button = event.target.closest("button");Подія може проходити через фази захоплення, цілі та спливання.
За замовчуванням обробники працюють під час спливання.
stopPropagation() зупиняє поширення події між елементами.
stopImmediatePropagation() додатково зупиняє наступні обробники на поточному елементі.
Жоден із цих методів не скасовує стандартну дію браузера.
Для скасування стандартної дії використовується preventDefault().
event.target — джерело події, а event.currentTarget — елемент поточного обробника.
Надмірне використання методів зупинки може зламати делегування подій і ускладнити підтримку коду.