Веб-сайты в браузере, терминал и другие интерактивные системы, устроены по одному и тому же принципу.
Они загружаются, затем переходят в режим ожидания и ждут, пока пользователь выполнит какое-либо действие. К таким действиям относятся клики, ввод текста, движения мыши, комбинации клавиш и так далее.
С точки зрения кода, существуют события, представляющие все действия. Рассмотрим некоторые распространенные события:
click
submit
keyup
keydown
focus
contextmenu
mouseover
mousedown
mouseup
События очень детализированы. Даже ввод буквы разбивается на два события: нажатие клавиши и ее отпускание. Кроме того, мы можем отличить горячие клавиши от обычного ввода с помощью события keypress
.
Любое событие будет связано с определенным элементом в DOM. Представьте, что вам нужно добавить новое поведение. Вы должны найти любой элемент и добавить коллбек addEventListener()
. Программа будет вызывать его, когда произойдет событие:
<button id="myButton"></button>
const button = document.getElementById('myButton');
// Добавляем обработчик, который вызовется при клике на элементе button
button.addEventListener('click', () => alert('Boom 1!'));
// Добавляем второй обработчик
button.addEventListener('click', () => alert('Boom 2!'));
Каждый обработчик событий — это функция, которая вызывается при наступлении события. Эти обработчики запускаются один за другим в том порядке, в котором они были определены.
При необходимости мы можем удалить обработчик, хотя на практике это случается редко:
const button = document.getElementById('myButton');
const handler = () => alert('Boom 1!');
button.addEventListener('click', handler);
// Важно, что сюда передается ровно та же самая функция по ссылке
button.removeEventListener('click', handler);
Мы можем вызывать события программно. Например, это относится к focus.
Представьте, что вы открываете чат и хотите написать в нем сообщение, но текст не появляется. Вам нужно сфокусироваться на поле ввода, чтобы набрать текст.
По умолчанию этого не происходит, и тут в дело вступаем мы:
const input = document.getElementById('textInput');
// Курсор появляется в поле для ввода
// Попробуйте поменять канал
// В большинстве мессенджеров вы увидите, что поле для ввода фокусируется автоматически
input.focus();
Как тестировать
Чтобы понять, как события работают, нужно регулярно использовать их в браузере. Проще всего это делать так:
- Открываем консоль на любом сайте. Смотрим, где находятся элементы, с которыми мы хотим работать — например, кнопки или формы
- Выбираем любой элемент, который хотим отработать. Для простоты можно взять
body
- Добавляем к нему обработчик
- Вызываем событие и смотрим на реакцию
const element = document.body;
element.addEventListener('click', () => console.log('wow!'));
// Теперь можно кликать по любой части сайта
wow!
// Как быть, если нужен элемент формы?
// Выбираем первый встреченный input
const element2 = document.querySelector('input');
// Вешаем обработчик и выводим на экран все, что нужно
element2.addEventListener('keyup', () => console.log('pressed!'));
Таким способом можно тестировать любые события на любых сайтах.
Объект события
Каждое возникающее событие имеет связанную с ним информацию, которая зависит от типа события.
Например, событие click
включает в себя щелчок и его координаты — точку на экране. Эта информация доступна через объект события, передаваемый обработчику события. Объекты событий передаются в обработчик всегда в виде одного параметра:
<div id="myElement">Бум!</div>
const button = document.getElementById('myElement');
button.addEventListener('click', (event) => {
// Обычный объект
console.log(event);
// Координаты точки, в которой произошел клик
console.log(event.clientX);
console.log(event.clientY);
});
Каждый клик по кнопке, будет приводить к созданию нового объекта event
со своими значениями, соответствующими текущему событию.
Объект event
наполнен множеством свойств, которые проще всего изучать прямо в браузере.
У разных событий есть как общие свойства, так и специфичные, например:
- у клика есть координаты
- у нажатия клавиши — ее значение
Подробнее о свойствах смотрите в документации.
Общие свойства:
event.target
— DOM-элемент, на котором произошло событие. Через него проще всего добраться до данных, которые могут понадобиться после событияevent.type
— имя события, например click, keyup и так далее
Для примера посмотрим на задачу валидации вводимого пароля. Будем подсвечивать поле для ввода красной рамкой, если пароль слишком короткий:
See the Pen js_dom_events by Hexlet (@hexlet) on CodePen.
Действие по умолчанию
Для некоторых элементов у браузера есть действия по умолчанию — они выполняются при срабатывании определенных событий. Представим для примера, что мы повесили обработчик на клик по ссылке. Выполнив клик, мы внезапно перейдем на другую страницу — ту, которая указана в атрибуте href
.
Это пример того самого действия по умолчанию, на которое никак не влияет наличие обработчиков. Чтобы отменить это действие, нужно вызвать метод event.preventDefault()
внутри обработчика:
<a href="#" id="myElement">Бум!</a>
const button = document.getElementById('myElement');
button.addEventListener('click', (e) => {
// Если этого не сделать, то браузер выполнит загрузку новой страницы
e.preventDefault();
alert(e.target.textContent);
});
Действиями по умолчанию обладают следующие элементы:
- Клик по ссылке приводит к переходу на страницу, указанную в href атрибуте
- Клик на кнопку с типом submit начинает отправку формы на сервер
- Вращение колесом мышки в
textarea
передвигает текст, если он не помещается - Вызов контекстного меню с помощью правого клика мышки
Конкуренция между событиями
При выполнении обработчиков могут возникать новые события — как от действий пользователя, так и от самих обработчиков. При этом некоторые события всегда возникают целым блоком — например mouseup
и click
.
Это не означает, что выполнение кода сразу переключается на обработку этих событий. Вместо этого события складываются в очередь и выполняются последовательно.
Но некоторые события все же берутся в обработку сразу. Это касается тех событий, которые генерируются программно — например, focus
.
Возникает закономерный вопрос: «Что происходит со страницей во время выполнения обработчика?». Здесь возможны варианты.
Представим, что обработчик выполняет некоторый код синхронно — например, занимается вычислениями. В этот момент блокируется все остальное и страница замирает. Если такое поведение длится слишком долго, то некоторые браузеры зависают, а другие — предлагают закрыть вкладку. Именно поэтому обработчики должны выполнять свою задачу максимально быстро.
А что, если задача асинхронная — например, выполнение запроса к серверу? В таком случае все продолжает прекрасно работать, потому что HTTP-запросы не блокируют выполнение кода.
Событийная система возможна только в асинхронном коде. По сути, при загрузке страницы происходит инициализация и установка обработчиков. Как правило, дальше не выполняется никакой код. Вся страница находится в ожидании действий от пользователя.
Частые ошибки
Довольно часто новички путаются в функциях. Вместо самой функции они передают в обработчик результат вызова функции:
const button = document.getElementById('myElement');
const handler = () => {
alert('Click!');
};
button.addEventListener('click', handler());
Функция handler()
выполняется в момент навешивания обработчика на событие. Вместо самой функции будет передан результат вызова handler()
.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.