Events

Событие - это сигнал браузера о действии пользователя.

Существует много видов событий, рассмотрим некоторые из них.

  • click - происходит, когда кликнули на элемент левой кнопкой мыши

  • submit - посетитель отправил форму

  • focus - посетитель фокусируется на элементе, например нажимает на input

  • keydown - посетитель нажимает клавишу

Список доступных событий

Порядок срабатывания событий

Одно действие может вызывать несколько событий. Например клик вызывает сначала mousedown, а затем mouseup и click. В тех случаях, когда одно действие генерирует несколько событий, их порядок фиксирован. То есть, обработчики вызовутся в порядке mousedown → mouseup → click.

Каждое событие обрабатывается независимо. Например при клике, события mouseup и click возникают одновременно, но обрабатываются последовательно. Сначала полностью завершается обработка mouseup, затем запускается click.

Действия браузера по умолчанию

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

Например:

  • Клик по ссылке инициирует переход на новый URL, указанный в href ссылки.

  • Отправка формы — перезагрузку страницы.

Для отмены действия браузера по умолчанию на объекте события есть стандартный метод.

event.preventDefault()

Распространение событий

Распространение событий (event propagation) — важная, но непонятная тема, когда речь идет о событиях. Это всеобъемлющий термин, который включает в себя три разных этапа жизни события: затопление, таргетинг и всплытие.

Распространение события двунаправленно — оно начинается на window, идет к целевому элементу и заканчивается на window. Распространение часто неправильно используется как синоним стадии всплытия. Каждый раз, когда происходит событие, происходит его распространение.

При наступлении события, оно проходит через три обязательные фазы:

  • Capture phase — событие начинается на window и тонет (проходит через все узлы-предки ) до самого глубокого целевого элемента где произошло событие.

  • Target phase — событие дошло до самого глубокого целевого элемента. Этот этап включает только уведомление узла на котором произошло событие.

  • Bubbling phase — заключительная фаза, событие всплывает от самого глубокого, целевого элемента, через все узлы-предки до window.

Мы рассмотрим фазу всплытия, так как она наиболее часто используемая и полезная. Для ознакомления с остальными фазами и более детальной информации прочитайте документацию на MDN.

Всплытие событий

Основной принцип всплытия — при наступлении события, обработчики сначала срабатывают на самом вложенном элементе, затем на его родителе, затем выше и так далее, вверх по цепочке вложенности. Всплывают почти все события, например события focus и blur не всплывают.

<div id="parent">
  PARENT
  <div id="child">
    CHILD
    <div id="inner-child">INNER-CHILD</div>
  </div>
</div>

Давайте рассмотрим пример, так будет понятнее. Есть три вложенных div, с обработчиками клика на каждом. Всплытие гарантирует, что клик по внутреннему inner-child вызовет обработчик клика, если есть, сначала на самом inner-child, затем на элементе child, далее на элементе parent и так далее вверх по цепочке родителей до window. Поэтому если в примере кликнуть на inner-child, то последовательно выведутся alert: inner-child → child → parent.

Этот процесс называется всплытием (event bubbling), потому что события всплывают от внутреннего элемента вверх через предков, подобно тому, как всплывает пузырек воздуха в воде.

Ссылка на пример в Codepen.io

event.target

Целевой элемент — на каком бы элементе мы ни поймали событие, всегда можно узнать где конкретно оно произошло. Самый глубокий элемент, который вызывает событие, называется целевым или исходным и доступен как event.target.

Отличия event.target и event.currentTarget:

  • event.target — это ссылка на исходный элемент на котором произошло событие, в процессе всплытия он неизменен.

  • event.currentTarget — это текущий элемент до которого дошло всплытие, на нём сейчас выполняется обработчик.

Если стоит только один обработчик на самом верхнем элементе, то он поймает все клики внутри родителя. Где бы ни был клик внутри, он всплывёт до элемента-родителя на котором сработает обработчик.

Ссылка на пример в Codepen.io

Прекращение всплытия

Обычно событие будет всплывать наверх до элемента window, вызывая все обработчики на своем пути. Но любой промежуточный обработчик может решить, что событие полностью обработано и остановить всплытие. Остановить всплытие можно, вызвав метод на объекте события.

event.stopPropagation()

Ссылка на пример в Codepen.io

Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены. То есть, stopPropagation препятствует продвижению события дальше, но на текущем элементе все обработчики выполнятся.

event.stopImmediatePropagation()

Используется для того, чтобы полностью остановить обработку события. Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.

Не прекращайте всплытие без необходимости, всплытие – это удобно! Прекращение всплытия создаёт свои подводные камни, которые потом приходится обходить.

Делегирование событий

Всплытие событий позволяет реализовать один из самых важных приёмов разработки — делегирование событий. Он заключается в том, что если есть много элементов, события которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому, мы ставим один обработчик на их общего предка. Из него можно получить целевой элемент event.target, понять на каком именно потомке произошло событие и обработать его.

Делегирование (event delegation) — это средство оптимизации интерфейса. Мы используем один обработчик для схожих действий на однотипных элементах.

Рассмотрим делегирование на примере. Создаем элемент div, добавляем в него двести параграфов и вешаем слушатели событий с функцией respondToTheClick к каждому параграфу.

Проблема в том, что у нас есть двести слушателей событий. Все они указывают на одну и ту же функцию слушателя, но самих слушателей 200! Что если мы переместим всех слушателей на общего родителя? 🤔

Теперь есть только один слушатель и браузеру не нужно хранить в памяти двести различных функций. Это - существенная оптимизация. Теперь для доступа к элементу на котором произошло событие, используем свойство target на объекте события в котором хранится ссылка на целевой элемент.

При клике в параграф происходит приблизительно следующее:

  • Произошел клик в элемент параграфа

  • Событие проходит этап захвата (capturing phase) и достигает цели

  • Теперь наступает фаза всплытия и событие поднимается по иерархии DOM-дерева

  • Когда событие всплывает до div, срабатывает слушатель и вызывается функция respondToTheClick

  • Внутри callback-функции, event.target — это элемент, на котором произошел клик. Таким образом, мы получаем прямой доступ к целевому элементу, абзацу, и можем работать с ним.

Такой подход имеет ряд преимуществ.

  • Упрощает инициализацию и экономит память — не нужно вешать много обработчиков.

  • Меньше кода — при добавлении и удалении элементов не нужно ставить или снимать обработчики.

  • Удобство изменений — можно массово добавлять или удалять элементы изменения, при этом на добавленных элементах будет весь функционал.

Last updated