Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Всплытие и делегирование событий

Когда на странице сто кнопок, вешать обработчик на каждую неудобно, а для кнопок, добавленных позже, — и вовсе не сработает. Помогает особенность событий: они поднимаются от элемента к его родителям. На этом построен приём, который называют делегированием.

Всплытие

Событие сначала срабатывает на элементе, где оно произошло, а затем по очереди на всех его родителях — до самого 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>

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.