Чтобы приложение было интерактивным, нам нужно понимать, что пользователь совершил то или иное действие на странице. Браузер распознает действия пользователя и создаёт событие.

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

События бывают разных типов: клик, нажатие клавиши на клавиатуре, прокрутка страницы и так далее. Происходящие события можно обрабатывать и выполнять код, когда нужное событие происходит. Например, при клике на кнопку показывать всплывающее окно.

Как пишется

Существует два способа обработать события:

  • с помощью on-свойств DOM-элементов;
  • методом addEventListener().

on-свойства DOM-элементов

Большинство событий связаны с DOM-элементами. Если пользователь кликнул на кнопку, то событие click связано с конкретным DOM-элементом — кнопкой, на которой кликнул пользователь.

Каждый DOM-элемент имеет большой набор свойств, которые начинаются на on:

  • onclick;
  • onscroll;
  • onkeypress;
  • onmouseenter;
  • и так далее.

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

const buttonElement = document.getElementById("change");
const squareDiv = document.getElementById("square");
 
// чтобы реагировать на нажатие кнопки, записываем функцию в свойство onclick.
// Эта функция будет вызываться при каждом нажатии на кнопку. Часто говорят,
// что эта функция обрабатывает событие
buttonElement.onclick = function () {
  squareDiv.style = `background-color: ${getColor()};`;
};
 
function getColor() {
  const colors = [
    "#49A16C",
    "#064236",
    "#ED6742",
    "#F498AD",
    "#1A5AD7",
    "#AFC9DA",
    "#FFD829",
    "#282A2E",
    "#5E6064",
    "#E6E6E6",
  ];
  return colors[Math.floor(Math.random() * colors.length)];
}

Чтобы перестать обрабатывать событие, нужно записать в свойство значение null.

Метод addEventListener()

🤖 Если обрабатывать события с помощью on-свойств, то получится добавить только одну функцию-обработчик на каждый элемент. Часто одного обработчика недостаточно. Чтобы не создавать ограничение на пустом месте, используют альтернативный метод подписки на события — метод addEventListener().

Метод вызывается у DOM-элемента. Аргументами нужно передать тип события (справочная информация) и функцию, которую нужно выполнить:

const buttonElement = document.getElementById("change");
const squareDiv = document.getElementById("square");
 
// чтобы реагировать на нажатие кнопки, подписываемся на событие click и передаем
// функцию-обработчик. Эта функция будет вызываться при каждом нажатии на кнопку
buttonElement.addEventListener("click", function () {
  squareDiv.style = `background-color: ${getColor()};`;
});

Как понять

Функция-обработчик

Функция-обработчик, или просто обработчик, — это функция, которая вызывается браузером при наступлении события.

При вызове браузер передаёт в обработчик объект события с помощью аргумента.

Объект события — это JavaScript-объект с информацией о событии. В объекте события есть как общие свойства (тип события, время события), так и свойства, которые зависят от типа события (например, на какую кнопку нажал пользователь).

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

window.addEventListener("keydown", function (event) {
  // используем объект события, чтобы получить информацию о нажатой клавише
  alert(`Вы нажали на кнопку: ${event.key}`);
});

Помимо объекта события, внутри функции можно использовать ключевое слово this. Оно позволяет получить DOM-элемент, на котором сработал обработчик. Это позволяет создать обработчик один раз, но привязать её к нескольким DOM-элементам.

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

function changeColor() {
  // меняем цвет кнопки, на которой произошло событие. кнопка доступна с помощью
  // ключевого слова this
  this.style = `background-color: ${getColor()};`;
}
 
const buttons = document.getElementsByTagName("button");
for (let i = 0; i < buttons.length; ++i) {
  const button = buttons[i];
  // к каждой кнопке привязываем обработчик
  button.addEventListener("click", changeColor); // обратите внимание, что мы не вызываем
  // функцию changeColor, а только пишем ее имя
}

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

Подробнее: 0009 Всплытие и погружение событий

Рассмотрим пример. У нас есть <div> элемент, в который вложено видео. Мы подписались на события click как на <div>, такч и на <video>. Если событие происходит на <div>, то мы меняем его цвет на случайный из списка. Если событие происходит на <video>, то мы запускаем видео. Попробуйте кликнуть на коробку:

const container = document.getElementById("container");
const video = document.getElementById("cat");
 
// обрабатываем событие click на <div>
container.addEventListener("click", function () {
  const colors = [
    "#49A16C",
    "#064236",
    "#ED6742",
    "#F498AD",
    "#1A5AD7",
    "#AFC9DA",
    "#FFD829",
    "#282A2E",
    "#5E6064",
  ];
  const randomColorIndex = Math.floor(Math.random() * colors.length);
  container.style = `background-color: ${colors[randomColorIndex]}`;
});
 
// обрабатываем событие click на видео
video.addEventListener("click", function () {
  this.currentTime = 0; // отматываем видео на начало
  this.play();
});

🤖 Обратите внимание, что событие срабатывает на обоих элементах — цвет фона меняется и запускается видео. Этому есть объяснение, оно называется всплытие событий (event bubbling).

Когда пользователь совершает действие, браузер ищет самый вложенный элемент, к которому относится событие. Затем это событие передаётся родительскому элементу и так далее до самого корня DOM.

В нашем примере мы кликнули на <video>, это самый вложенный элемент. Браузер создал событие, и мы обработали его в коде. После этого браузер передаёт событие родителю <video> (то есть элементу, который содержит <video>) — элементу <div>. Мы получаем его и обрабатываем. И он всплывает дальше, пока не дойдёт до <body>.

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

Если кликнуть по блокам на демо, то можно увидеть, как событие всплывает вверх к родителям:

let active;
let counter = 0;
 
// обрабатываем событие click на всех <div>
let divs = Array.from(document.querySelectorAll("div")).reverse();
for (let i = 0; i < divs.length; ++i) {
  const isLast = i + 1 === divs.length;
  divs[i].addEventListener("click", clickHandlerGenerator(isLast));
}
 
function clickHandlerGenerator(isLast = false) {
  return function () {
    let me = this;
    setTimeout(function () {
      if (active) {
        active.classList.remove("active");
      }
      me.classList.add("active");
 
      active = me;
 
      if (isLast) {
        setTimeout(function () {
          active.classList.remove("active");
          active = undefined;
          counter = 0;
        }, 300);
      }
    }, counter * 300);
    ++counter;
  };
}

Всплытие события можно остановить с помощью метода stopPropagation() у объекта события:

video.addEventListener("click", function (event) {
  event.stopPropagation();
  this.currentTime = 0;
  this.play();
});