Событийная модель

Время чтения: 7 мин

Кратко

Секция статьи "Кратко"

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

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

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

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

Как пишется

Секция статьи "Как пишется"

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

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

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

Секция статьи "on-свойства DOM-элементов"

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

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

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

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

        
          
          let buttonElement = document.getElementById('change');let 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)];}
          let buttonElement = document.getElementById('change');
let 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

Секция статьи "Метод addEventListener"

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

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

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

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

        
        
          
        
      
Открыть демо в новой вкладке

Как понять

Секция статьи "Как понять"

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

Секция статьи "Функция-обработчик"

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

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

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

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

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

        
        
          
        
      

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

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

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

let buttons = document.getElementsByTagName('button');
for (let i = 0; i < buttons.length; ++i) {
  let button = buttons[i];
  // к каждой кнопке привязываем обработчик
  button.addEventListener('click', changeColor); // обратите внимание, что мы не вызываем
  // функцию changeColor, а только пишем ее имя
}

        
        
          
        
      
Открыть демо в новой вкладке

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

Секция статьи "Всплытие событий"

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

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

// обрабатываем событие click на <div>
container.addEventListener('click', function() {
  const colors = ["#49A16C", "#064236", "#ED6742", "#F498AD", "#1A5AD7", "#AFC9DA",
                  "#FFD829", "#282A2E", "#5E6064"];
  let 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) {  let 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;  }}
          let active;
let counter = 0;

// обрабатываем событие click на всех <div>
let divs = Array.from(document.querySelectorAll('div')).reverse();
for (let i = 0; i < divs.length; ++i) {
  let 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()})
          video.addEventListener("click", function (event) {
  event.stopPropagation()
  this.currentTime = 0
  this.play()
})

        
        
          
        
      

На практике

Секция статьи "На практике"

nlopin

Секция статьи "nlopin"

🛠 Всегда подписывайся на события с помощью addEventListener, так ты избежишь доработок, когда потребуется повесить несколько обработчиков на одно и то же событие.

🛠 Если нужно обработать все события определённого типа, вызови метод addEventListener у объекта window:

        
          
          // обрабатываем все клики на страницеwindow.addEventListener("click", function () {  alert("clicked")})
          // обрабатываем все клики на странице
window.addEventListener("click", function () {
  alert("clicked")
})

        
        
          
        
      

🛠 Если не используешь объект события в обработчике, то не указывай его в списке параметров обработчика. Вместо function (event) {…} напиши function() {…}.

🛠 Всплытие событий — важный концепт, попробуй с ним поэкспериментировать.