Всплытие и погружение
Событие проходит три фазы: погружение, цель, всплытие.
Фазы
<div class="outer">
<div class="inner">
<button>Клик</button>
</div>
</div>
При клике на кнопку:
- Capture — от
documentвниз к кнопке - Target — сама кнопка
- Bubble — от кнопки вверх к
document
Всплытие
По умолчанию обработчики срабатывают на фазе всплытия — от цели вверх:
inner.addEventListener('click', () => console.log('inner'));
outer.addEventListener('click', () => console.log('outer'));
// клик по button:
// inner
// outer
Погружение
Опция capture: true:
outer.addEventListener('click', () => console.log('outer capture'), true);
inner.addEventListener('click', () => console.log('inner capture'), true);
// клик по button:
// outer capture
// inner capture
Порядок — сверху вниз.
target и currentTarget
outer.addEventListener('click', (event) => {
event.target; // button — где кликнули
event.currentTarget; // outer — где обработчик
});
target не меняется, currentTarget — элемент с обработчиком.
stopPropagation
Останавливает всплытие:
inner.addEventListener('click', (event) => {
event.stopPropagation();
});
outer.addEventListener('click', () => {
console.log('не сработает');
});
stopImmediatePropagation
Останавливает и всплытие, и другие обработчики на том же элементе:
button.addEventListener('click', (event) => {
event.stopImmediatePropagation();
console.log('1');
});
button.addEventListener('click', () => {
console.log('2'); // не сработает
});
preventDefault
Отменяет поведение браузера:
form.addEventListener('submit', (event) => {
event.preventDefault(); // без перезагрузки
});
link.addEventListener('click', (event) => {
event.preventDefault(); // без перехода
});
Делегирование
Главный практический приём. Один обработчик на родителе вместо многих на детях:
ul.addEventListener('click', (event) => {
const li = event.target.closest('li');
if (!li) return;
console.log('клик по', li.textContent);
});
Плюсы:
- Работает для динамически добавленных элементов
- Меньше обработчиков — меньше памяти
- Проще код
Пример: таблица
table.addEventListener('click', (event) => {
const button = event.target.closest('button[data-action]');
if (!button) return;
const row = button.closest('tr');
const id = row.dataset.id;
if (button.dataset.action === 'delete') {
deleteRow(id);
}
});
Пример: модалка
modal.addEventListener('click', (event) => {
if (event.target === modal) {
closeModal();
}
});
Клик по фону (не по содержимому) закрывает.
Отмена в capture
Можно перехватить до того, как обработчики на элементе сработают:
document.addEventListener('click', (event) => {
if (event.target.closest('.no-click')) {
event.stopPropagation();
}
}, true);
События, которые не всплывают
focus, blur, mouseenter, mouseleave, load — не всплывают.
Аналоги, которые всплывают:
focus→focusinblur→focusoutmouseenter→mouseovermouseleave→mouseout
Итоги
- Фазы: capture → target → bubble
- По умолчанию — всплытие
capture: trueдля погруженияstopPropagationостанавливаетpreventDefaultотменяет поведение- Делегирование — главный приём