Клавиша / esc

.addEventListener()

Учим приложение реагировать на действия пользователя.

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

Кратко

Скопировано

Добавляет элементу действие, которое будет выполнено после срабатывания события. Например, на клик мышки или нажатие клавиши.

Пример

Скопировано

Найдём первую кнопку на странице и будем выводить сообщение в консоль, когда произошёл клик по этой кнопке.

        
          
          const element = document.querySelector('button')element.addEventListener('click', function (event) {  console.log('Произошло событие', event.type)})
          const element = document.querySelector('button')

element.addEventListener('click', function (event) {
  console.log('Произошло событие', event.type)
})

        
        
          
        
      

Как пишется

Скопировано

Сигнатура функции выглядит следующим образом:

        
          
          element.addEventListener(eventType, handler, options)
          element.addEventListener(eventType, handler, options)

        
        
          
        
      
  • element — любой HTMLElement на странице;
  • eventType — строка, содержащая название события. Наиболее популярные события 'click', 'change', 'submit', 'keydown', 'keyup', 'mousemove', 'mouseenter', 'mouseleave';
  • handler — функция, которая будет вызвана, когда событие произойдёт;
  • options/capture — необязательный параметр, который определяет дополнительные характеристики обработки события;
    • capture — включает или выключает обработку события в фазе погружения. Принимает значение true или false, по умолчанию false;
    • options: { capture: bool, passive: bool, once: bool, signal: AbortSignal } — при передаче объекта аргумент будет распознан как объект настроек, так можно установить больше параметров;
      • passive — значение true означает что внутри handler никогда не будет вызвана функция event.preventDefault(), если функция event.preventDefault() всё-таки вызвана, браузер должен её игнорировать и выводить предупредительное сообщение в консоль;
      • once — включает автоматическую отписку от события после первого срабатывания;
      • signal — передаётся ссылка на объект сигнала AbortSignal, который позволяет отписаться от события.

Ниже приведено несколько вариантов вызова функции с разными параметрами:

        
          
          function handleMouseClick(event) {  console.log('Вы нажали на элемент:', event.target)}window.addEventListener('click', handleMouseClick)window.addEventListener('click', handleMouseClick, true)window.addEventListener('click', handleMouseClick, false)window.addEventListener('click', handleMouseClick, {  passive: true,  capture: false})const abortController = new AbortController()window.addEventListener('click', handleMouseClick, {  signal: abortController.signal})
          function handleMouseClick(event) {
  console.log('Вы нажали на элемент:', event.target)
}

window.addEventListener('click', handleMouseClick)
window.addEventListener('click', handleMouseClick, true)
window.addEventListener('click', handleMouseClick, false)
window.addEventListener('click', handleMouseClick, {
  passive: true,
  capture: false
})

const abortController = new AbortController()

window.addEventListener('click', handleMouseClick, {
  signal: abortController.signal
})

        
        
          
        
      

У объекта event есть специальные методы, такие как preventDefault() и stopPropagation(). Остальные методы практически не используются:

  • preventDefault() позволяет заблокировать стандартное поведение браузера. Например, по клику на ссылке — заблокировать переход по этой ссылке.
  • stopPropagation() позволяет остановить распространение события по DOM-дереву.

Как понять

Скопировано

При вызове функции, в неё передаётся специальный объект (в примере выше — event), который у разных типов событий разный. Например, у событий нажатия клавиши есть код клавиши, а у событий перемещения мыши — координаты.

Функция может быть объявлена ранее:

        
          
          const element = document.querySelector('button')function handleClickFunction(event) {  alert('Именованная функция')}element.addEventListener('click', handleClickFunction)
          const element = document.querySelector('button')

function handleClickFunction(event) {
  alert('Именованная функция')
}
element.addEventListener('click', handleClickFunction)

        
        
          
        
      

А может быть анонимной:

        
          
          element.addEventListener('click', function (event) {  alert('Анонимная функция')})
          element.addEventListener('click', function (event) {
  alert('Анонимная функция')
})

        
        
          
        
      

🤖 Заранее созданные функции обычно используют, когда функция содержит в себе много кода или к ней нужно ссылаться несколько раз. Например, когда нужно отписаться от события позже. Для отписки используется метод элемента Element.removeEventListener().

Альтернативный способ отписки от события можно реализовать с помощью объекта AbortController. Подробнее о нём читайте в разделе «На практике».

Анонимные функции удобно использовать при быстрой разработке или когда обработчик создаётся в одном единственном месте и выносить его в отдельную именованную функцию — дольше, чем писать код самой этой функции. В этом случае очень часто используют короткую, стрелочную запись функции:

        
          
          element.addEventListener('click', (event) => {  alert('Анонимная функция')})
          element.addEventListener('click', (event) => {
  alert('Анонимная функция')
})

        
        
          
        
      

Обработка события в фазе погружения

Скопировано

Фаза погружения — это первый этап жизни события, когда оно движется от корня документа (window → document → html → ...) к целевому элементу.

Добавить обработчик, который будет отрабатывать на этой фазе, просто:

        
          
          element.addEventListener('click', handler, true);// илиelement.addEventListener('click', handler, { capture: true });
          element.addEventListener('click', handler, true);
// или
element.addEventListener('click', handler, { capture: true });

        
        
          
        
      

Третий аргумент addEventListener может быть либо булевым значением, либо объектом. Булевое значение true автоматически интерпретируется как { capture: true }, а его отсутствие или false — как { capture: false }.

Это сделано для обратной совместимости, т.к. изначально существовал только булевый параметр useCapture. Когда спецификация эволюционировала и появилась необходимость добавлять другие опции (например, once, passive), разработчики ввели объектный синтаксис. Чтобы не сломать миллионы существующих сайтов, старый синтаксис оставили, но внутри браузера он приводится к новому.

Идентификация обработчиков событий

Скопировано

Если добавить один и тот же обработчик несколько раз с одинаковыми параметрами capture — новые копии не добавятся. Будет обработан только первый добавленный обработчик, остальные удалять не нужно.

Уникальность обработчика определяется тремя параметрами:

  • тип события (например, 'click' или 'mousedown');
  • ссылка на функцию-обработчик;
  • флаг фазы (capture: true или capture: false).

В этом примере добавится только один обработчик события:

        
          
          function handlerClick () {  console.log('click')}// неявно { capture: true }document.addEventListener('click', handlerClick, true)// второй обработчик для той же фазы не добавитсяdocument.addEventListener('click', handlerClick, { once: true, capture: true })
          function handlerClick () {
  console.log('click')
}

// неявно { capture: true }
document.addEventListener('click', handlerClick, true)
// второй обработчик для той же фазы не добавится
document.addEventListener('click', handlerClick, { once: true, capture: true })

        
        
          
        
      

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

        
          
          function handlerClick () {  console.log('click')}document.addEventListener('click', handlerClick, true)document.addEventListener('click', handlerClick, { passive: true, capture: false })
          function handlerClick () {
  console.log('click')
}

document.addEventListener('click', handlerClick, true)
document.addEventListener('click', handlerClick, { passive: true, capture: false })

        
        
          
        
      

Улучшение производительности скролла

Скопировано

В JavaScript существуют отменяемые события (cancelable event) - это события, в которых с помощью preventDefault() можно отменить действие по умолчанию (прокрутку, переход по ссылке, отправку формы и т. п.). В таких событиях выполнение действия по-умолчанию откладывается до завершения работы обработчиков. Браузер ждёт выполнения всех обработчиков события, чтобы убедиться, что ни один из них не вызывает preventDefault().

Например, при обработке событий (touchmove, touchstart, wheel и mousewheel), может возникать заметная задержка прокрутки, особенно на мобильных устройствах.

Флаг { passive: true } решает эту проблему. Вы говорите браузеру: «Обещаю, я не буду вызывать preventDefault() внутри этого обработчика». Получив такое обещание, браузер больше не ждёт и начинает прокручивать немедленно, параллельно выполняя ваш код. Такой обработчик событий также называется «пассивный слушатель».

        
          
          element.addEventListener('wheel', handleScroll, { passive: true });
          element.addEventListener('wheel', handleScroll, { passive: true });

        
        
          
        
      

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

💡 Если вы укажете { passive: true }, но попытаетесь вызвать preventDefault(), браузер:

  • проигнорирует вызов (в консоли может быть предупреждение);
  • скролл всё равно произойдёт.

💡 Также для события scroll флаг { passive: true } устанавливать не нужно, т.к. scroll — это уже результат прокрутки, он не может блокировать прокрутку страницы, в отличие от touchmove, touchstart, wheel и mousewheel.

💡 Начиная с Chrome 56 (2017 год), браузеры включили это поведение по умолчанию для touchstart, touchmove и wheel событий на глобальных объектах (window, document, body)

То есть, если вы пишете:

        
          
          window.addEventListener('touchstart', handler);
          window.addEventListener('touchstart', handler);

        
        
          
        
      

Современный Chrome обработает его так, как если бы вы явно указали { passive: true }.

На практике

Скопировано

Павел Минеев советует

Скопировано

🛠 Обработка передачи третьего параметра для устаревших браузеров.

Проверим, поддерживает ли браузер объект options. Добавим обработчик события на window, передав ему объект options. В нём поле passive будет менять ранее установленную переменную на true при попытке доступа к объекту. Если браузер проверит содержимое options.passive, он поддерживает данные настройки.

        
          
          let hasPassiveSupport = falsetry {  const options = Object.defineProperty({}, 'passive', {    get() {      hasPassiveSupport = true    },  })  window.addEventListener('test', null, options)} catch (err) {}
          let hasPassiveSupport = false

try {
  const options = Object.defineProperty({}, 'passive', {
    get() {
      hasPassiveSupport = true
    },
  })

  window.addEventListener('test', null, options)
} catch (err) {}

        
        
          
        
      

Далее можем просто проверить, поддерживается ли passive. Если поддерживается, то передаём options, иначе — false.

        
          
          window.addEventListener(  'resize',  function () {    // Обработка события  },  hasPassiveSupport ? { passive: true } : false)
          window.addEventListener(
  'resize',
  function () {
    // Обработка события
  },
  hasPassiveSupport ? { passive: true } : false
)

        
        
          
        
      

В случае, если используете passive и capture одновременно, такую проверку можно не делать. Старый браузер воспримет переданный объект как true и включит capture, а новый обработает оба параметра внутри объекта.

        
          
          window.addEventListener('resize', function () {  // Обработка события}, { passive: true, capture: true })
          window.addEventListener('resize', function () {
  // Обработка события
}, { passive: true, capture: true })

        
        
          
        
      

Дока Дог советует

Скопировано

🛠 Базовая обработка событий клавиатуры.

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

        
          
          <div class="event">Ожидание ввода...</div><input type="text" placeholder="Введите сообщение"><div class="result"></div>
          <div class="event">Ожидание ввода...</div>
<input type="text" placeholder="Введите сообщение">
<div class="result"></div>

        
        
          
        
      

Для этого подпишемся на событие keydown. Каждое нажатие клавиши будет создавать событие 'keydown' и функция будет срабатывать. Внутри функции будем получать код клавиши из свойства key объекта события. Если код клавиши оказался 'Enter', то будем сбрасывать значение в поле ввода и выводить результат.

        
          
          const element = document.querySelector('input')element.addEventListener('keydown', function (event) {  const message = '<code>' + event.key + '</code>'  const value = event.target.value  if (event.key === 'Enter' && value.length > 0) {    const messageElement = document.createElement('div')    messageElement.classList.add('message')    messageElement.innerText = value    document.querySelector('.result').appendChild(messageElement)    event.target.value = ''  }  document.querySelector('.event').innerHTML = message})
          const element = document.querySelector('input')

element.addEventListener('keydown', function (event) {
  const message = '<code>' + event.key + '</code>'
  const value = event.target.value

  if (event.key === 'Enter' && value.length > 0) {
    const messageElement = document.createElement('div')

    messageElement.classList.add('message')
    messageElement.innerText = value
    document.querySelector('.result').appendChild(messageElement)
    event.target.value = ''
  }

  document.querySelector('.event').innerHTML = message
})

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

🛠 Предотвращение срабатывания события по умолчанию.

В этом примере мы заменим стандартное поведение в случае, когда пользователь кликает на ссылку. Чтобы стандартное поведение не сработало, нужно вызывать метод preventDefault у события.

        
          
          <a href="https://yandex.ru" target="_blank">  Ссылка на Яндекс</a><a href="https://yandex.ru" target="_blank" id="custom">  Ссылка с изменённым поведением</a><div id="result"></div>
          <a href="https://yandex.ru" target="_blank">
  Ссылка на Яндекс
</a>
<a href="https://yandex.ru" target="_blank" id="custom">
  Ссылка с изменённым поведением
</a>
<div id="result"></div>

        
        
          
        
      

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

        
          
          const linkElement = document.querySelector('#custom')const resultElement = document.querySelector('#result')linkElement.addEventListener('click', function (event) {  event.preventDefault()  resultElement.innerText = 'Вы нажали на ссылку, но ничего не произошло!'  setTimeout(function () {    resultElement.innerText = ''  }, 2500)})
          const linkElement = document.querySelector('#custom')
const resultElement = document.querySelector('#result')

linkElement.addEventListener('click', function (event) {
  event.preventDefault()

  resultElement.innerText = 'Вы нажали на ссылку, но ничего не произошло!'
  setTimeout(function () {
    resultElement.innerText = ''
  }, 2500)
})

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

Окенчиц Владислав советует

Скопировано

🛠 Базовый пример использования AbortController для отписки от слушателя событий.

По умолчанию в большинстве случаев для отписки стоит использовать removeEventListener. Но, если при подписке была использована анонимная функция, то отписаться от такого слушателя через removeEventListener не получится, так как вторым параметром необходимо передать ссылку на функцию-обработчик.

        
          
          const abortController = new AbortController()const element = document.querySelector('#element1')element.addEventListener('click', () => console.log('Подписка активна'), {  signal: abortController.signal})// Вызываем, когда захотим отписаться:abortController.abort()
          const abortController = new AbortController()
const element = document.querySelector('#element1')

element.addEventListener('click', () => console.log('Подписка активна'), {
  signal: abortController.signal
})

// Вызываем, когда захотим отписаться:
abortController.abort()

        
        
          
        
      

🛠 Отписка сразу от нескольких обработчиков.

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

        
          
          const abortController = new AbortController()const element1 = document.querySelector('#element1')const element2 = document.querySelector('#element2')element1.addEventListener('click', () => {  // ...}, {signal: abortController.signal})element2.addEventListener('click', () => {  // ...}, {signal: abortController.signal})// Отписываемся одним вызовом сразу от двух обработчиковabortController.abort()
          const abortController = new AbortController()
const element1 = document.querySelector('#element1')
const element2 = document.querySelector('#element2')

element1.addEventListener('click', () => {
  // ...
}, {signal: abortController.signal})
element2.addEventListener('click', () => {
  // ...
}, {signal: abortController.signal})

// Отписываемся одним вызовом сразу от двух обработчиков
abortController.abort()

        
        
          
        
      

🛠 Вешаем слушатель событий на AbortController.

В случае, если необходимо реализовать логику после отписки, то можно слушать событие abort на AbortSignal:

        
          
          const abortController = new AbortController()const signal = abortController.signalsignal.addEventListener('abort', () => {  console.log('Операция отменена')})
          const abortController = new AbortController()
const signal = abortController.signal

signal.addEventListener('abort', () => {
  console.log('Операция отменена')
})