Браузерные события элементов Ч. 1


События (events) позволяют пользователю взаимодействовать с элементами на странице.

Для этого нужно научиться перехватывать действия пользователя на странице: клики по элементам, движения мышки, нажатия клавиш.

У события есть две фазы:
  • погружение или захват capture
  • всплытие bubble
По умолчанию все события обрабатываются при всплытии.

Представим, что у нас есть html-документ, внутри которого div, внутри которого ссылка а.
Примерно так:

<!DOCTYPE html>
<html>
  <body>
    <div>
      <a href="https://google.ru/" id="link">google.ru</a>
    </div>
  </body>
</html>

Рассмотрим клик по ссылке.

1. Браузер генерирует событие event. Событие это тоже объект (в JavaScript вообще практически всё является объектом кроме разве что примитивов) со своим набором полей.

2. Событие погружается по DOM-дереву - capture:

документ - html - body - div - a

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

3. Всплытие  - bubble происходит в противоположном направлении

a - div - body - html - документ

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

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

Для отслеживания кликов по ссылке напишем следующий код:

const link = document.querySelector("#link");

link.addEventListener("click", function(){
  alert("Кликнули по ссылке");
});

Теперь при клике по сылке появляется всплывающее окно с текстом "Кликнули по ссылке", а когда мы его закрываем, происходит переход по ссылке. Такая последовательность объявняется тем что .addEventListener отслеживакет событие когда оно дошло до целевого элемента и только начало всплывать, а событие по умолчанию происходит когда всплытие уже закончилось.

События по умолчанию


У некоторых элементов есть действие по умолчанию default action. Например, при клике по ссылке происходит переход по этой ссылке.
Как уже было сказано выше, действие по умолчанию происходит когда закончилось всплытие.


Что означает фраза "обработчик сработал". Браузер выполнил код, который был задан в функции, указанной внутри обработчика. Поэтому события браузерные - раз именно браузер отслеживает появление события и выполняет код заданный в функции, указанной внутри обработчика события.

На каждый элемент можно навесить не один, а несколько обработчиков событий.

Как отменять браузерные события

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

Для удаления браузерных событий существует специальный метод: removeEventListener

Но следующий код не сработает и второй обработчик не отменит первый.

  link.addEventListener("click", function(){
    alert("кликнули по ссылке");
  });
 
  link.removeEventListener("click", function(){
  alert("Кликнули по ссылке");
  });

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

Поэтому, если планируем браузерное событие отключать, функцию, которая ему соответствует, необходимо прописывать отдельно, а потом и в addEventListener, и в removeEventListener передавать не саму функцию, а ссылку на неё.
Примерно так:

  function clickHandler(){
    alert("кликнули по ссылке");
  }
   
  link.addEventListener("click",
clickHandler);

  link.removeEventListener("click", clickHandler);

Теперь всё работает и второй обработчик отменяет первый.

Разумеется, функция, которую мы добавляем, может быть объявлена и как function declaration (пример выше) и как function expression.
Так тоже будет работать:

  const clickHandler = function (){
    alert("кликнули по ссылке");
  }
   
  link.addEventListener("click", clickHandler);
   
  link.removeEventListener("click", clickHandler);

Перехват событий на других элементах DOM-дерева


Изменим предыдущий html-код. Теперь у нас не ссылка, а кнопка:

<!DOCTYPE html>
<html>
  <body>
    <button id="myButton">Нажми</button>
</body>
</html>

Добавим два обработчика события: один на кнопку, другой на тело документа:

  const myButton = document.querySelector("#myButton");
  const body = document.querySelector("body");

  myButton.addEventListener("click", () => {
    console.log("Событие прошло через кнопку")
  });
   
  body.addEventListener("click", () => {
    console.log("Событие прошло через body")
  });

Результат в консоли:

Событие прошло через кнопку
Событие прошло через body

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

Выполнения событий при погружении


Все события по умолчанию выполняются при всплытии. Но последовательность выполнения событий можно изменить. Для этого у функции .addEventListener есть третий параметр - capture:

addEventListener(event, handler, capture)
  • event - событие, которое перехватывается;
  • handler - какая функция при этом выполнится;
  • capture - обработка события при всплытии или при погружении.
Если третий аргумент указан true, это означает, что перехват события будет происходить при погружении.


Изменим предыдущий скрипт:

  const myButton = document.querySelector("#myButton");
  const body = document.querySelector("body");

  myButton.addEventListener("click", () => {
    console.log("Событие прошло через кнопку")
  }, true);
   
  body.addEventListener("click", () => {
    console.log("Событие прошло через body")
  }, true);


Результат в консоли:

Событие прошло через body
Событие прошло через кнопку 

Последовательность выполнения событий изменилась, теперь они выполняются при погружении, а не при всплытии.

На елемент можно добавить обработчики как на всплытие, так и на погружение.

Зачем это нужно?
Перехват событий при погружении это просто возможность. Использовать её или нет решает разработчик, исходя из своих задач. Например, перехватывая события на стадии погружения можно перехватить все клики. Возможность перехватывать события на стадии погружения используется также при делегировании.

Аргумент event


Для функции, которая срабатывает при выполнении события, браузер автоматически добавляет аргумент event (впрочем, название аргумента можно изменить, например, е или evt или как-нибудь иначе).
event это тоже объект, как и почти что всё в JavaScript, в поля которого записывается информация о событии.

Основные поля события:

event.type — имя события
event.target — элемент, для которого изначально было предназначено событие
event.currentTarget — элемент, который перехватил событие в данный момент
event.eventPhase —  фаза события (захват, выполнение, всплытие) 

У event.eventPhase —  цифровые обозначения:

  • Event.NONE = 0
  • Event.CAPTURING_PHASE = 1
  • Event.AT_TARGET = 2
  • Event.BUBBLING_PHASE = 3
Event.CAPTURING_PHASE - событие произошло при погружении
Event.BUBBLING_PHASE - событие произошло при всплытии
Event.AT_TARGET - событие произошло на том элементе, для которого предназначалось

Выполним следующий код:

  const myButton = document.querySelector("#myButton");
  const body = document.querySelector("body");

  myButton.addEventListener("click", (event) => {
    console.log("Событие прошло через кнопку");
    console.log(event.eventPhase);
  }, true); 
 
  body.addEventListener("click", (event) => {
    console.log("Событие прошло через body");
    console.log(event.eventPhase);
  }, true);
 
  body.addEventListener("click", (event) => {
    console.log("Событие прошло через body");
    console.log(event.eventPhase);
  });

Результат:

Событие прошло через body
1
Событие прошло через кнопку
2
Событие прошло через body
3

Вначале событие проходит через body на стадии погружения, значение event.eventPhase 1.
Затем через кнопку, которая является целевым элементом, значение event.eventPhase 2.
Затем через body на стадии всплытия, значение event.eventPhase 3.

Посмотрим, что из себя представляет поле  event.target.
Выполним код:

  const myButton = document.querySelector("#myButton");
  const body = document.querySelector("body");

  myButton.addEventListener("click", (event) => {
    console.log("Событие прошло через кнопку");
    console.log(event.target);
  }); 
 
  body.addEventListener("click", (event) => {
    console.log("Событие прошло через body");
    console.log(event.target);
  });


Результат:

Событие прошло через кнопку
  <button id=​"myButton">​Нажми​</button>​
Событие прошло через body
  <button id=​"myButton">​Нажми​</button>​

event.target показывает весь html-код целевого элемента. Результат можно сократить, если вместо event.target использовать свойство event.target.id. Таким образом можно получить не весь код, а только id целевого элемента.

Таким образом благодаря event.target можно узнать для какого целевого элемента предназначалось событие, которое сейчас обрабатывается. Эта возможность используется в делегировании.

Отмена событий по умолчанию

В html-коде у нас есть ссылка

<a href="https://google.ru/" id="myLink">google</a>

Отменить переход по ссылке позволяет следующий код:

  const myLink = document.querySelector("#myLink");
 
  myLink.addEventListener("click", (event) => {
    event.preventDefault();
  });


Методы stopPropagation и stopImmediatePropagation


Выполним следующий код:

  myLink.addEventListener("click", (event) => {
    alert("Событие прошло через ссылку");
  });

  body.addEventListener("click", (event) => {
    event.stopPropagation();

  }, true);

Событие срабатывает на body на погружение. Метод event.stopPropagation останавливает дальнейшее распространение события. Поэтому до ссылки клик не доходит а alert на ссылке не срабатывает. Тем не менее, переход по ссылке происходит. Метод event.stopPropagation позволяет не выполнять функции, указанные в нашем коде, но не отменяет событие по умолчанию.

Метод stopImmediatePropagation не только останавливает дальнейшее распространение события, но и позволяет игнорировать другие обработчики события на этом же элементе.

Продолжение: https://studyjavascript.blogspot.com/2019/03/2.html