Всплытие и делегирование событий
Когда на странице сто кнопок, вешать обработчик на каждую неудобно, а для кнопок, добавленных позже, — и вовсе не сработает. Помогает особенность событий: они поднимаются от элемента к его родителям. На этом построен приём, который называют делегированием.
Всплытие
Событие сначала срабатывает на элементе, где оно произошло, а затем по очереди на всех его родителях — до самого document. Это называется всплытием. Нажмите кнопку:
<div id="box">
<button>Нажми</button>
</div>
<script>
document.querySelector('#box').addEventListener('click', () => {
console.log('Клик дошёл до div');
});
</script>Обработчик стоит на div, а не на кнопке, но клик по кнопке до него всплыл.
target и currentTarget
В обработчике на родителе важно различать два свойства события: event.target — элемент, по которому кликнули, и event.currentTarget — элемент, на котором стоит обработчик:
<div id="box">
<button>Кнопка</button>
</div>
<script>
document.querySelector('#box').addEventListener('click', (event) => {
console.log(event.target.tagName); // BUTTON
console.log(event.currentTarget.tagName); // DIV
});
</script>Делегирование
Делегирование — это один обработчик на общем родителе вместо обработчиков на каждом элементе. По event.target он узнаёт, где именно был клик:
<ul id="menu">
<li>Главная</li>
<li>Каталог</li>
<li>Контакты</li>
</ul>
<script>
document.querySelector('#menu').addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log(`Выбран пункт: ${event.target.textContent}`);
}
});
</script>Новые элементы
Главное преимущество: делегирование работает и для элементов, которые появятся позже. Обработчик стоит на списке, поэтому новым пунктам ничего вешать не нужно:
<button id="add">Добавить задачу</button>
<ul id="tasks"></ul>
<script>
const tasks = document.querySelector('#tasks');
let count = 0;
document.querySelector('#add').addEventListener('click', () => {
count++;
const item = document.createElement('li');
item.textContent = `Задача ${count}`;
tasks.append(item);
});
tasks.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
event.target.remove();
}
});
</script>Добавьте несколько задач, а потом щёлкните по любой — она удалится.
Остановка всплытия
Метод event.stopPropagation() останавливает всплытие: родители о событии не узнают. Пользуются им редко, когда вложенный элемент должен вести себя по-своему:
<div id="card">
Карточка товара
<button>В избранное</button>
</div>
<script>
document.querySelector('#card').addEventListener('click', () => {
console.log('Открываем товар');
});
document.querySelector('button').addEventListener('click', (event) => {
event.stopPropagation();
console.log('Добавлено в избранное');
});
</script>Частая ошибка
Проверять event.target, когда внутри кнопки есть другие теги. Клик по выделенному слову или иконке даёт в target именно их, а не кнопку. Надёжнее искать нужный элемент методом closest():
<div id="toolbar">
<button data-action="save"><b>Сохранить</b></button>
</div>
<script>
document.querySelector('#toolbar').addEventListener('click', (event) => {
const button = event.target.closest('button');
if (button) {
console.log(button.dataset.action); // save
}
});
</script>