логотип PurpleSchool

Browser Events: Погружение в события браузера и всплытие с новыми знаниями

09 августа 2023 г.
360 просмотров
фото команды
Автор

Валерий

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

Виды событий

Событие является уведомлением от браузера о совершении определенного действия или изменении состояния элемента веб-страницы. Их можно условно разделить на:

  • События устройств ввода (обычно клавиатура и мышь): "click", "mousedown" и "mouseup", позволяют обрабатывать клики, а "keydown", "keyup" и "keypress"
    • отслеживать ввод с клавиатуры.
  • События элементов управления(инпуты, формы и т.п.): "focus" и "blur" позволяют контролировать фокус, "change"
    • изменение элемента, “submit”
      • отправку формы.
  • События CSS: “aninationstart”
    • CSS-анимация началась, “transitionend" – CSS-переход завершен и т.д.
  • События документа: DOMContentLoaded – DOM готов, beforeunload – пользователь покидает страницу, load – внешние ресурсы загружены, стили применены и т.д.

Если Вам необходимо получить информацию о конкретном событии и его описание, рекомендую обратиться к подробному справочнику на MDN (https://developer.mozilla.org/ru/docs/Web/Events). В данной статье мы разберем взаимодействие с событиями и начнем с трех вариантов их обработки.

В HTML разметке

Что бы обработать событие мы можем просто добавить нужному тегу специальный атрибут (названия таких атрибуты состоят из “on” и названия события, как onclick, onchange и т.д.) и передать в него функцию, которая должна сработать при активации события. Вместо функции можно передать код, который так же исполнится при активации события, но такое описание чувствительно к формату скобок и сработает только если сам код будет обернут в двойные скобки(”код”), а строки в нем в одинарные(’строка’):

    <script type="text/javascript">
        const toThank = () =>{console.log('Thanks!')}
    </script>
    <button onclick="console.log('Thanks!')">Click me</button>
    <button onclick="toTank()">Click me</button> 
    //эти две строки кода отработают идетично
    <button onclick="console.log("Thanks!")">Click me</button> 
    //а здесь будет ошибка из-за неправельных скобок

Через DOM-свойство

Обработчик, который мы создали в предыдущем примере, будет храниться в свойстве DOM-объекта. Мы можем обратиться к такому свойству чтобы создать обработчик, а название такого свойства совпадает с названием атрибута и чувствительно к регистру:

    <button id="myButton">Click me</button>
    <script type="text/javascript">
        const button = document.getElementById("myButton");
        button.onclick = () =>  console.log("Thanks!");
    </script>

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

    <button id="myButton">Click me</button>
    <script type="text/javascript">
        const toThank = () =>{console.log('Thanks!')}
        const button = document.getElementById("myButton");
        button.onclick = toThank();
        //не сработает потому что функция сразу вызывается
        button.setAttribute('onclick', toThank);
        //тоже не будет работать потому что превратится в строку
    </script>

Что бы удалить обработчик, который повесили через атрибут или свойство, можно переназначить его и передать в него функцию, которая возвращает false. В разметке такой трюк, в основном, применяется для отмены действий по умолчанию (переход по ссылкам, отправка формы и подобное), но лучше использовать специальный метод preventDefault.

    <button onclick="console.log('Thanks!')" id="myButton">Click me</button>
    <script type="text/javascript">
        const button = document.getElementById("myButton");
        button.onclick = () => {return false}
        //так мы назначим новый обработчик и отменим действие
    </script>
    <a href="/" onclick="return false">Click me</a>
    <a href="/" onclick="event.preventDefault()">Click me</a>
    //а в этих двух примерах - отменили действие по умолчанию 
    //теперь ссылка не откроется при нажатии

С помощью метода addEventListener

Оба предыдущий способа имеют серьезный недостаток - нельзя повесить несколько обработчиков на одно событие. Для таких случаев есть специальный метод addEventListener который принимает название события, функцию и необязательные параметры настроек: element.addEventListener(event, handler, [options]);. В настройках есть следующие опции: once:true означает что после срабатывания обработчик будет удален, passive:true сообщает браузеру что слушатель не будет отменять действие по умолчанию и capture к которому мы вернемся немного позже.

    <button id="myButton">Click me</button>
    <script type="text/javascript">
      const button = document.getElementById("myButton");
      const handler1 = () => {console.log('Thanks!')}
      const handler2 = () => {console.log('Thanks again!')}

      button.onclick = () => {console.log('Hi!')};
      button.addEventListener('click', handler1);
      button.addEventListener('click', handler2);
    </script>

Существуют события, которые могут быть обработаны только с использованием метода addEventListener. Одним из них является DOMContentLoaded, которое возникает, когда загрузка и построение DOM документа завершены.

В отличии от объявления в разметке или через DOM-свойство, не предусмотрено возможности получить обработчики созданные с помощью addEventListener из элемента. Поэтому существует метод removeEventListener который принимает строго те же параметры что и addEventListener, с помощью которого был добавлен слушатель. Важно заметить что, если при создании слушателя функция была описана в нем, а не объявлена в другой части кода, такой слушатель невозможно удалить:

    button.addEventListener("click", () => {console.log("Thanks")});
    button.removeEventListener("click", () => {console.log("Thanks")});
    //не сработает т.к. это две разные функции
    const toTank = () => {console.log("Thanks")};
    button.addEventListener("click", toTank);
    button.removeEventListener("click", toTank);
    //здесь передана одна и та же функция и все сработает

Жизненный цикл события (погружение и всплытие)

В примерах ранее мы рассматривали довольно простые случаи с нажатием на единичные кнопки и ссылки, а если их много и мы должны отслеживать их все? Или если мы повесим слушатель на элемент, а пользователь нажмет на вложенный, у которого нет слушателя? Придется вешать слушатели на каждый? На самом деле нет потому что почти все события в браузере проходят три фазы: погружение (capturing), фаза цели (target phase) и всплытие (bubbling stage). С их помощью мы можем поймать событие, даже если на целевом элементе (тот на котором произошло событие) нет слушателя.

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

<div id="myDiv">
  <button id="button-1">Click me</button>
  <button id="button-2">Click me too</button>
</div>
<script type="text/javascript">
  const sayHi = (event) => {
    setTimeout(() => {
      console.log(`Hello I am div, but the target is ${event.target.id}!`);
    }, 0);
  };
  const element = document.getElementById("myDiv");
  element.addEventListener("click", sayHi);
</script>
//Несмотря на то что на кнопках нет обработчиков 
//собитие нажатия на них всплывет до div и он поздаровается
//сообщив id нажатой кнопки

Всплытие можно остановить вызвав метод event.stopPropagation() на последнем элементе, обработчик которого нужно запустить или event.stopImmediatePropagation() что бы кроме остановки всплытия предотвратить и срабатывание обработчика.

Вернемся к остальным фазам жизненного цикла события. Фазу цели нельзя обработать отдельно, а для обработки погружения нужно при объявлении обработчика через addEventListener передать опциональным параметром {capture: true} или просто true, если другие опции слушателя не заданы. Как уже понятно из названия, погружение аналогично всплытию, но в этой фазе событие опускается по цепочке вложенных элементов к целевому.

<div>
  <button >Click me</button>
</div>
<script type="text/javascript">
    //веваем слушателей на все элементы страницы
  for(let element of document.querySelectorAll('*')) {
    element.addEventLstener("click", e => console.log(element.tagName), true);
  }
</script>
//В консоль будут выведены все теги по цепочке до нашей кнопки:
//HTML
//BODY
//DIV
//BUTTON

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

Заключение

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

изображение курса

Основы JavaScript

Антон Ларичев
иконка часов18 часов лекций
иконка зведочки рейтинга4.8
Основы JavaScript