Скидки до 60% и 3 курса в подарок 0 дней 00 :00 :00 Выбрать курс
Код Справочник по фронтенду
#статьи

Как работают события в JavaScript

Магия, которая делает страницы интерактивными.

Иллюстрация: Polina Vari для Skillbox Media

Почти всё, что делает сайт интерактивным, связано с событиями. Кликнули по кнопке — событие. Навели курсор на элемент — событие. Что-то ввели в форму, прокрутили страницу, закрыли всплывающее окно — это всё тоже события.

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

В этой статье разберём, что такое события в JavaScript, зачем они нужны и как с ними работать на практике — на примерах, которые встречаются в каждом втором проекте.

Содержание


Что такое события в JavaScript

События в JavaScript — это сигналы, которые сообщают, что на странице что-то произошло. Эти сигналы могут исходить от пользователя, от самого браузера или даже от элементов на странице.

Рассмотрим самые популярные типы событий:

  • События мыши происходят, когда пользователь кликает кнопкой мыши, двигает её или наводит курсор на элемент. Самые популярные: клик мыши по элементу (click), двойной клик мыши (dblclick), наведение курсора на элемент (mouseover), увод курсора с элемента (mouseout).
  • События клавиатуры срабатывают, когда пользователь нажимает или отпускает клавишу на клавиатуре: нажатие клавиши (keydown) и отпускание клавиши (keyup).
  • События формы позволяют реагировать на действия с формами: отправку (submit), изменение данных в поле (change), ввод текста (input).
  • События загрузки используются для того, чтобы что-то сделать, когда страница полностью загружена (load) или, наоборот, когда пользователь пытается закрыть страницу (beforeunload).
  • Браузерные события, например изменение размера окна (resize) или прокрутка страницы (scroll).

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

Как назначать обработчики событий

Чтобы JavaScript мог реагировать на события, ему нужен обработчик события. Это специальная функция или просто блок кода, которые запускаются, когда происходит заданное событие. Есть несколько способов подключить обработчик к элементу. Рассмотрим их по порядку — от старого к современному.

Через HTML-атрибуты (onclick, onchange)

Это самый старый и простой способ: прямо в разметке HTML указываем, какой код выполнить при событии.

<button onclick="alert('Кнопка нажата!')">Нажми меня</button>

При таком подходе логика (JS) смешивается с разметкой (HTML), а это затрудняет поддержку кода и масштабирование. При этом для каждого события на элементе страницы можно задать только один обработчик. Этот подход считают устаревшим и стараются не использовать в реальных проектах.

Через свойства DOM-элемента
(element.onclick = …)

Следующий вариант чуть лучше: мы выбираем элемент с помощью JavaScript и записываем обработчик в его специальное свойство.

const button = document.querySelector('button');

button.onclick = function() {
  alert('Кнопка нажата!');
};

В этом коде:

  • document — это встроенный объект JavaScript, который представляет весь HTML-документ (страницу) в браузере. С его помощью можно искать элементы, изменять их, добавлять классы и так далее.
  • .querySelector('button') — это метод, который ищет первый элемент на странице, соответствующий CSS-селектору в скобках. Здесь 'button' — CSS-селектор для тега <button>. Значит, браузер найдёт первую кнопку на странице.
  • const button = ... — мы сохраняем найденный элемент в переменную button. Теперь вы можете обратиться к этой переменной и назначить обработчики событий.
  • button.onclick — специальное свойство объекта button, предназначенное для хранения функции, которая будет запущена, когда произойдёт событие click на кнопке.

При таком подходе всё ещё можно назначить только один обработчик на одно событие. Если потом написать button.onclick = НовоеДействие, старый обработчик просто перезапишется.

Через addEventListener — современный способ

Самый распространённый способ — использование метода addEventListener. Он универсальный, гибкий и применяется почти во всех современных проектах.

Такой подход даёт больше возможностей:

  • можно назначить несколько обработчиков на одно и то же событие;
  • легко удалить обработчик, если он больше не нужен;
  • можно настроить поведение — например, чтобы обработчик сработал только один раз.
const button = document.querySelector('button');

button.addEventListener('click', function() {
  alert('Кнопка нажата!');
});

button.addEventListener('click', function() {
  console.log('Этот код тоже сработает при клике.');
});

Что здесь происходит:

  • Сначала мы находим кнопку на странице — document.querySelector('button').
  • Затем добавляем первый обработчик: при клике появится окно с сообщением.
  • Добавляем второй обработчик на то же событие. Он выведет сообщение в консоль.

Оба обработчика работают независимо друг от друга — оба срабатывают при каждом клике. Это как раз то, чего нельзя сделать с атрибутом onclick.

Как назначить обработчик на конкретный элемент

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

В HTML можно указать:

<button id="buy-button">Купить</button>
<button class="add-to-cart">В корзину</button>

У первой кнопки есть атрибут id, у второй — class. Эти названия можно использовать в JavaScript, чтобы выбрать конкретный элемент и повесить на него обработчик.

const buyButton = document.querySelector('#buy-button');
const cartButton = document.querySelector('.add-to-cart');

Здесь:

  • #buy-button — кнопка с id="buy-button";
  • .add-to-cart — кнопка с классом add-to-cart.

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

Как удалить обработчик события

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

В JavaScript это делает метод removeEventListener.

Пример:

function handleClick() {
  console.log('Клик!');
}

// Добавим обработчик
button.addEventListener('click', handleClick);

// А потом удалим его
button.removeEventListener('click', handleClick);

Что здесь важно:

  • Мы создаём функцию handleClick — она показывает сообщение в консоли.
  • Добавляем её как обработчик события click.
  • Позже удаляем этот же обработчик — с помощью removeEventListener.

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

Особенности разных событий в Javascript

События в JavaScript работают почти одинаково, но у каждого типа есть свои особенности. Некоторые срабатывают чаще, некоторые требуют осторожности. Разберём, на что стоит обратить внимание при работе.

События мыши
(click, dblclick, mouseover, mouseout)

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

  • click — самое распространённое событие. Срабатывает при нажатии.
  • dblclick срабатывает при двойном клике. В вебе его используют редко, потому что пользователи почти всегда кликают один раз.
  • mouseover и mouseout часто применяют для показа и скрытия всплывающих подсказок. Но у них есть особенность: они срабатывают не только наведении на элемент и выходе за его границы, но и при движении между его вложенными блоками.

События клавиатуры
(keydown, keyup)

Такие события назначают либо на весь документ (document), либо на конкретные поля — например, на текстовое поле, или textarea.

  • keydown срабатывает сразу при нажатии клавиши.
  • keyup — когда клавишу отпускают.

Клавиши вроде Ctrl, Alt и функциональных F1F12 тоже вызывают эти события.

События формы
(submit, change, input)

События формы позволяют реагировать на действия пользователя при работе с полями ввода и при отправке данных.

  • submit назначают на саму форму (<form>). Оно срабатывает при отправке — например, при нажатии кнопки или клавиши Enter.
  • change работает тогда, когда пользователь меняет значение и уходит из поля. Подходит для выпадающих списков, чекбоксов и радиокнопок.
  • input срабатывает каждый раз, когда пользователь вводит символ. Подходит для автоподсказок и проверки текста на лету.

События загрузки
(load, beforeunload)

Эти события связаны с моментом загрузки страницы или уходом с неё.

  • load обычно назначают на window. Оно срабатывает, когда загружаются все ресурсы: HTML, CSS, картинки, скрипты. Это хороший момент для запуска основного JavaScript-кода.
window.addEventListener('load', () => {
  console.log('Страница полностью загружена');
});
  • beforeunload нужен, чтобы предупредить пользователя перед закрытием страницы, — например, если он заполнил форму, но не отправил её. Но есть ограничение: браузеры больше не позволяют показывать свой текст в этом сообщении. Вместо него появляется стандартная фраза вроде «Вы действительно хотите покинуть эту страницу? Данные, которые вы ввели, могут не сохраниться».

Это сделали специально, чтобы сайты не могли пугать пользователей или вводить их в заблуждение.

Другие события
(resize, scroll, error)

  • resize срабатывает при изменении размера окна. Его обычно вешают на window.
  • scroll тоже назначают на window или на контейнер с прокруткой. Это очень частое событие, поэтому при работе с ним важно следить за производительностью.
  • error можно использовать для отслеживания ошибок загрузки изображений, скриптов или JavaScript-кода. Это событие помогает собирать информацию о сбоях в работе сайта.

У разных событий и элементов есть свои нюансы использования. Давайте рассмотрим каждый из них.

Ограничения на частый вызов событий в Javascript

Некоторые события вроде resize или scroll могут срабатывать по сотне раз в секунду, пока пользователь тянет уголок окна или крутит колесо мыши. Если внутри такого обработчика находится сложный код (например, пересчёт размеров, перестройка вёрстки или запросы на сервер), это может заметно замедлить страницу или даже подвесить браузер.

Чтобы с этим справиться, используют специальные приёмы — throttle и debounce.

throttle запускает обработчик не чаще одного раза за указанный интервал времени. Например, раз в 200 миллисекунд. Даже если событие сработает 50 раз за эти 200 мс, код выполнится только один раз.

let waiting = false; // Флаг, показывающий, ждём ли мы окончания паузы

window.addEventListener('resize', () => {
  // Если сейчас не ждём, значит, можем выполнить код
  if (!waiting) {
    console.log('Window resized (throttle)!'); // Выводим сообщение в консоль

    waiting = true; // Ставим флаг, что теперь ждём

    setTimeout(() => {
      waiting = false; // Через 200 мс сбрасываем флаг и можем снова выполнять код
    }, 200);
  }
});

Debounce работает иначе: ждёт, пока пользователь закончит действие. Если пользователь, например, тянет окно туда-сюда, обработчик не срабатывает вообще. Код запускается только после того, как всё стихло, — например, через 200 миллисекунд после последнего события.

let timer; // Переменная для хранения id таймера

window.addEventListener('resize', () => {
  clearTimeout(timer); // Сбрасываем старый таймер, если он ещё не успел выполниться

  timer = setTimeout(() => {
    console.log('Window resized (debounce)!'); // Выводим сообщение в консоль
  }, 200); // Запускаем функцию через 200 мс после последнего resize
});

Как события проходят по странице: три этапа

Когда пользователь кликает на кнопку, событие срабатывает не сразу на ней. Сначала оно идёт от самого верха страницы (от document) к этой кнопке, потом — обратно наверх. Это не баг, а встроенный механизм JavaScript. Он позволяет ловить события не только на элементе, где всё произошло, но и на его родителях.

Событие проходит три этапа — их называют фазами. Разберём их на примере.

Допустим, у нас в HTML есть вот такая структура:

<div id="box">
  <button id="btn">Нажми меня</button>
</div>

Пользователь нажимает на кнопку. Но браузер отправляет сигнал не сразу на неё. Сначала он идёт сверху вниз — от document к box, потом к кнопке. После этого событие разворачивается и возвращается вверх — от кнопки к box, потом к document.

Эти три этапа и есть фазы события:

  • Фаза захвата: событие идёт сверху вниз.
  • Фаза цели: событие добралось до элемента, на котором произошёл клик.
  • Фаза всплытия: событие идёт обратно — снизу вверх.

Как это выглядит на практике.

Добавим обработчики на кнопку и на её родителя — блок box.

// Обработчик на родителе
document.getElementById('box').addEventListener('click', () => {
  console.log('box -- сработал при всплытии');
});

// Обработчик на кнопке
document.getElementById('btn').addEventListener('click', () => {
  console.log('button -- сработал');
});

Если вы кликнете на кнопку, в консоли появится:

button -- сработал
box -- сработал при всплытии

Сначала событие сработает на кнопке — это фаза цели. Потом оно поднимется вверх, и сработает обработчик на box — это фаза всплытия.

Что делать, если событие должно сработать до того, как дойдёт до кнопки

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

Например, у нас есть кнопка «Сохранить» внутри формы. Если пользователь кликнет по ней слишком рано, когда данные ещё не загрузились, форма отправится с пустыми полями. Чтобы этого не случилось, мы можем навесить обработчик на форму целиком и использовать фазу захвата. Тогда событие сработает сначала на форме — мы успеем проверить, всё ли готово, и остановить отправку, если понадобится.

Чтобы поймать событие на этом этапе, нужно явно указать { capture: true }.

document.getElementById('box').addEventListener('click', () => {
  console.log('box -- сработал при захвате (capture)');
}, { capture: true });

document.getElementById('btn').addEventListener('click', () => {
  console.log('button -- сработал');
});

Теперь при клике вы увидите в консоли:

box -- сработал при захвате (capture)
button -- сработал

Сначала сработал box, потому что событие шло вниз. Потом — кнопка, когда событие дошло до неё.

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

Запрет вызова родительского события при обработке дочернего

Обычно на странице есть вложенные элементы, а у каждого из них свои действия при клике. Например, пользователь нажимает на кнопку, и срабатывает обработчик у самой кнопки, а потом ещё и у её родителя. Это происходит потому, что событие «всплывает» вверх по дереву: сначала оно срабатывает на элементе, где всё произошло, а потом поднимается к его родителям.

Так работает большинство событий в браузере — и чаще всего это удобно. Но бывают случаи, когда такое поведение не подходит.

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

Пользователь нажимает «Отправить». Но при этом срабатывает не только кнопка — событие «всплывает» вверх, доходит до модального окна, которое тут же закрывается. Получается, что форма не только отправилась, но ещё и закрылась, чего не было в планах.

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

Пример:

child.addEventListener('click', (event) => {
  console.log('Clicked on child');
  event.stopPropagation(); // Остановим всплытие
});

Что здесь происходит:

  • Пользователь нажимает на кнопку.
  • Срабатывает обработчик кнопки.
  • Внутри него мы вызываем event.stopPropagation().
  • Это останавливает дальнейшее распространение события вверх — родитель больше о нём ничего не узнает.

Такой приём тоже не нужен в большинстве случаев, но его полезно знать.

Делегирование событий: вешаем событие на схожие элементы

На странице часто бывает много похожих элементов — например, список кнопок, ссылок или карточек. Можно, конечно, повесить обработчик клика на каждую кнопку отдельно, но это неудобно и тормозит страницу, особенно если таких элементов много.

Вместо этого мы можем повесить один обработчик на общий родительский элемент, например на весь список. А дальше — просто проверять, где именно произошёл клик. Такой приём называется делегированием событий.

У нас есть HTML-список:

<ul id="list">
  <li>Элемент 1</li>
  <li>Элемент 2</li>
  <li>Элемент 3</li>
</ul>

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

const list = document.getElementById('list');

list.addEventListener('click', (event) => {
  // event.target — это элемент, на который кликнули
  if (event.target.tagName === 'LI') {
    console.log(`Клик по элементу: ${event.target.textContent}`);
  }
});

Что здесь происходит:

  • Мы навесили один обработчик кликов на весь список <ul>.
  • Когда пользователь кликает на любой <li>, событие автоматически поднимается, и срабатывает обработчик на <ul>.
  • Внутри обработчика есть event — это специальный объект, который браузер передаёт автоматически. Он хранит всю информацию о событии: где кликнули, какой кнопкой мыши и так далее.
  • Свойство event.target указывает на конкретный элемент, по которому кликнули. Мы проверяем: если кликнули именно по <li>, то выводим его текст в консоль.

Типичные ошибки при работе с событиями в JavaScript

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

Удаление обработчика не работает

// Добавили обработчик с анонимной функцией
button.addEventListener('click', function () {
  console.log('Clicked!');
});

// Пытаемся удалить
button.removeEventListener('click', function () {
  console.log('Clicked!');
});

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

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

// Создаём функцию заранее
function handleClick() {
  console.log('Clicked!');
}

// Добавляем обработчик
button.addEventListener('click', handleClick);

// И удаляем точно такую же функцию
button.removeEventListener('click', handleClick);

Обработчик срабатывает несколько раз

// Первый раз — всё нормально
button.addEventListener('click', () => {
  console.log('Clicked!');
});

// Второй раз — добавляется ещё один обработчик
button.addEventListener('click', () => {
  console.log('Clicked again!');
});

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

Как избежать:

  • Убедиться, что обработчик добавляется один раз.
  • Использовать { once: true }, если нужно выполнить действие только один раз:
button.addEventListener('click', () => {
  console.log('Clicked once!');
}, { once: true });

Форма или ссылка перезагружает страницу

document.querySelector('form').addEventListener('submit', () => {
  console.log('Форма отправлена!');
});

Что происходит: по умолчанию форма отправляет данные и перезагружает страницу. Ваш код может не успеть ничего сделать — данные не сохранятся, сообщения не покажутся.

Как избежать: остановить стандартное поведение с помощью event.preventDefault().

document.querySelector('form').addEventListener('submit', (event) => {
  event.preventDefault(); // Отменяем стандартное поведение (перезагрузку)
  console.log('Форма отправлена без перезагрузки!');
});

Практика: тренируемся работать с событиями в JavaScript


Упражнение 1: обработка отправки формы без перезагрузки

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

Исходные данные (HTML):

<form id="signup-form">
  <input type="text" name="username" placeholder="Введите имя" />
  <button type="submit">Отправить</button>
</form>

Подсказка: чтобы получить введённое пользователем имя из формы, используйте выражение form.elements.username.value.

Решение:

const form = document.getElementById('signup-form');

form.addEventListener('submit', (event) => {
  event.preventDefault(); // Останавливаем стандартную отправку формы
  const username = form.elements.username.value;
  console.log(`Имя пользователя: ${username}`);
});

Упражнение 2: подсветить выбранный пункт списка

Задание: есть список элементов. При клике на любой пункт он должен подсветиться, а у других подсветка должна убраться.

Исходные данные:

HTML

<ul id="menu">
  <li>Главная</li>
  <li>О нас</li>
  <li>Контакты</li>
</ul>

CSS

.active {
  background-color: lightblue;
}

Подсказка: чтобы добавить стиль для элемента, используйте .classList.add(), например, event.target.classList.add. Чтобы удалить стиль — .classList.remove().

Решение:

const menu = document.getElementById('menu');

menu.addEventListener('click', (event) => {
  if (event.target.tagName === 'LI') {
    // Убираем подсветку у всех элементов
    for (let item of menu.children) {
      item.classList.remove('active');
    }
    // Добавляем подсветку выбранному
    event.target.classList.add('active');
  }
});

Упражнение 3: проверка поля email при вводе с подсветкой ошибки

Задание: на странице есть поле для ввода email. Сделайте так, чтобы:

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

Исходные данные (HTML):

<input type="email" id="email" placeholder="Введите ваш email" />

Подсказки: для проверки email используйте следующее выражение.

onst emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 
// Создаём регулярное выражение — это шаблон, по которому можно проверить, похожа ли строка на email
// ^ — начало строки
// [^\s@]+ — одна или несколько букв или цифр, кроме пробелов и символа @
// @ — обязательно должен быть символ @
// [^\s@]+ — после @ снова одна или несколько букв или цифр, кроме пробелов и @
// \. — обязательно точка
// [^\s@]+ — после точки снова одна или несколько букв или цифр
// $ — конец строки

Для изменения рамки элемента используйте .style.borderColor = ''.

Решение:

const emailInput = document.getElementById('email');

// Простая регулярка для проверки email
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

emailInput.addEventListener('input', () => {
  const value = emailInput.value;

  if (!emailPattern.test(value)) {
    // Если email не подходит, подсвечиваем красной рамкой
    emailInput.style.borderColor = 'red';
  } else {
    // Если email правильный, убираем подсветку
    emailInput.style.borderColor = '';
  }
});

Больше интересного про код — в нашем телеграм-канале.  Подписывайтесь!



Изучайте IT на практике — бесплатно

Курсы за 2990 0 р.

Я не знаю, с чего начать
4 бесплатных курса для старта в IT ➞
Переходите в Telegram и пройдите 4 курса по топовым направлениям IT. Определите, какая сфера вам ближе, и сделайте первый шаг к новой профессии.
Пройти курс→
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована