логотип PurpleSchool
Иконка входа
Вход
логотип PurpleSchool

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

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

Виды событий

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

  • События устройств ввода (обычно клавиатура и мышь): "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 к загруженному ресурсу или странице поэтому они не погружается и не всплывают, но это скорее исключения, потому что большинство событий проходят все фазы.

Заключение

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

Карта развития разработчика

Получите полную карту развития разработчика по всем направлениям: frontend, backend, devops, mobile

Комментарии

0

Карта развития разработчика

Получите полную карту развития разработчика по всем направлениям: frontend, backend, devops, mobile