Events
Браузерные события
Для реакции на действия посетителя и внутреннего взаимодействия скриптов существуют события.
Событие– это сигнал от браузера о том, что что-то произошло. Существует много видов событий. Посмотрим список самых часто используемых, пока просто для ознакомления:
События мыши:
click
– происходит, когда кликнули на элемент левой кнопкой мышиcontextmenu
– происходит, когда кликнули на элемент правой кнопкой мышиmouseover
– возникает, когда на элемент наводится мышьmousedown
иmouseup
– когда кнопку мыши нажали или отжалиmousemove
– при движении мыши
События на элементах управления:
submit
– посетитель отправил форму<form>
focus
– посетитель фокусируется на элементе, например: нажимает на<input>
Клавиатурные события:
keydown
– когда посетитель нажимает клавишуkeyup
– когда посетитель отпускает клавишу
События документа:
DOMContentLoaded
– когда HTML загружен и обработан, DOM документа полностью построен и доступен.
События CSS:
transitionend
– когда CSS-анимация завершена.
Назначение обработчиков событий
Событию можно назначить обработчик, то есть функцию, которая сработает, как только событие произошло.
Именно благодаря обработчикам JavaScript-код может реагировать на действия посетителя.
Есть несколько способов назначить событию обработчик. Сейчас мы их рассмотрим, начиная от самого простого.
Использование атрибута HTML
Обработчик может быть назначен прямо в разметке в атрибуте, который называетсяon<событие>
.
Например, чтобы прикрепитьclick
-событие кinput
кнопке, можно присвоить обработчикonclick
таким образом:
При клике мышкой, на кнопке выполнится код, указанный в атрибутеonclick
.
Обратите внимание: для содержимого атрибутаonclick
используются одинарные кавычки, так как сам атрибут находится в двойных.
Частая ошибка новичков в том, что они забывают, что код находится внутри атрибута. Запись видаonclick="alert("Клик!")"
с двойными кавычками внутри не будет работать. Если вам действительно нужно использовать именно двойные кавычки, то это можно сделать, заменив их на"
, то есть так:onclick="alert("Клик!")"
.
Однако обычно этого не требуется, так как прямо в разметке пишутся только очень простые обработчики. Если нужно сделать что-то сложное, то имеет смысл описать это в функции, и в обработчике вызвать уже её.
Следующий пример по клику запускает функциюcountRabbits()
.
Как мы помним, атрибут HTML-тега не чувствителен к регистру, поэтому ONCLICK
будет работать так же, какonClick
или onCLICK
… Но, как правило, атрибуты пишут в нижнем регистре:onclick
.
Использование свойства DOM-объекта
Можно назначать обработчик, используя свойство DOM-элементаon<событие>
.
Пример установки обработчикаclick
:
Если обработчик задан через атрибут, то браузер читает HTML-разметку, создаёт новую функцию из содержимого атрибута и записывает в свойствоonclick
.
Этот способ, по сути, аналогичен предыдущему.
Обработчик хранится именно в DOM-свойстве, а атрибут – лишь один из способов его инициализации.
Эти два примера кода работают одинаково:
HTML + JS:
Так как DOM-свойствоonclick
, в итоге одно, то назначить более одного обработчика нельзя.
В примере ниже назначение через JavaScript перезапишет обработчик из атрибута:
Кстати, обработчиком можно назначить и уже существующую функцию:
Доступ к элементу через this
Внутри обработчика событияthis
ссылается на текущий элемент, то есть на тот, на котором он сработал.
Это можно использовать, чтобы получить свойства или изменить элемент.
В коде нижеbutton
выводит свое содержимое, используяthis.innerHTML
:
Недостаток назначения через свойство
Фундаментальный недостаток описанных выше способов назначения обработчика – невозможность повесить несколько обработчиков на одно событие.
Например, одна часть кода хочет при клике на кнопку делать ее подсвеченной, а другая – выдавать сообщение. Нужно в разных местах два обработчика повесить.
При этом новый обработчик будет затирать предыдущий. Например, следующий код на самом деле назначает один обработчик – последний:
Разработчики стандартов достаточно давно это поняли и предложили альтернативный способ назначения обработчиков при помощи специальных методов, которые свободны от указанного недостатка.
addEventListener и removeEventListener
МетодыaddEventListener
иremoveEventListener
являются современным способом назначить или удалить обработчик, и при этом позволяют использовать сколько угодно любых обработчиков.
Назначение обработчика осуществляется вызовомaddEventListener
с тремя аргументами:
event
Имя события, напримерclick
handler
Ссылка на функцию, которую надо поставить обработчиком.
phase
Необязательный аргумент, «фаза», на которой обработчик должен сработать.
Удаление обработчика осуществляется вызовомremoveEventListener
:
МетодaddEventListener
позволяет добавлять несколько обработчиков на одно событие одного элемента, например:
Как видно из примера выше, можно одновременно назначать обработчики и через DOM-свойство, и черезaddEventListener
. Однако во избежание путаницы, рекомендуется выбрать один способ.
Порядок обработки событий
События могут возникать не только по очереди, но и «пачкой» по много сразу. Возможно и такое, что во время обработки одного события возникают другие, например пока выполнялся код дляonclick
– посетитель нажал кнопку на клавиатуре (событиеkeydown
).
Главный поток
В каждом окне выполняется только один главный поток, который занимается выполнением JavaScript, отрисовкой и работой с DOM.
Он выполняет команды последовательно, может делать только одно дело одновременно и блокируется при выводе модальных окон, таких какalert
.
Очередь событий
Произошло одновременно несколько событий или во время работы одного случилось другое – как главному потоку обработать это?
Если главный поток прямо сейчас занят, то он не может срочно выйти из середины одной функции и прыгнуть в другую. А потом третью. Отладка при этом могла бы превратиться в кошмар, потому что пришлось бы разбираться с совместным состоянием нескольких функций сразу.
Когда происходит событие, оно попадает в очередь.
Внутри браузера непрерывно работает «главный внутренний цикл», который следит за состоянием очереди и обрабатывает события, запускает соответствующие обработчики и т.п.
Иногда события добавляются в очередь сразу пачкой.
Например, при клике на элементе генерируется несколько событий:
Сначала
mousedown
– нажата кнопка мыши.Затем
mouseup
– кнопка мыши отпущена, и так как это было над одним элементом, то дополнительно генерируетсяclick
(два события сразу).
Таким образом, при нажатии кнопки мыши в очередь попадёт событиеmousedown
, а при отпускании – сразу два события:mouseup
иclick
. Браузер обработает их строго одно за другим:mousedown
→mouseup
→click
.
При этом каждое событие из очереди обрабатывается полностью отдельно от других.
Вложенные (синхронные) события
Обычно возникающие события «становятся в очередь».
Но в тех случаях, когда событие инициируется не посетителем, а кодом, то оно, как правило, обрабатывается синхронно, то есть прямо сейчас.
Рассмотрим в качестве примера событиеonfocus
.
Пример: событие onfocus
Когда посетитель фокусируется на элементе, возникает событиеonfocus
. Обычно оно происходит, когда посетитель кликает на поле ввода, например:
При фокусе на поле, оно изменит значение.
Но ту же фокусировку можно вызвать и явно, вызовом методаelem.focus()
:
При этом обработчикonclick
вызовет методfocus()
на текстовом полеtext
. Код обработчикаonfocus
, который при этом запустится, сработает синхронно, прямо сейчас, до завершенияonclick
.
Объект события
Чтобы хорошо обработать событие, недостаточно знать о том, что это – «клик» или «нажатие клавиши». Могут понадобиться детали: координаты курсора, введенный символ и другие, в зависимости от события.
Детали произошедшего браузер записывает в «объект события», который передается первым аргументом в обработчик.
Свойства объекта события
Пример ниже демонстрирует использование объекта события:
Свойства объектаevent
:
event.type
Тип события, в данном случаеclick
event.currentTarget
Элемент, на котором сработал обработчик. Значение – в точности такое же, как и уthis
, но бывают ситуации, когда обработчик является методом объекта, и егоthis
при помощиbind
привязан к этому объекту, тогда мы можем использоватьevent.currentTarget
.
event.clientX / event.clientY
Координаты курсора в момент клика (относительно окна).
Всплытие и перехват
Основной принцип всплытия:
При наступлении события, обработчики сначала срабатывают на самом вложенном элементе, затем на его родителе, затем выше и так далее, вверх по цепочке вложенности.
Например, есть 3 вложенных элементаFORM > DIV > P
с обработчиком на каждом:
Всплытие гарантирует, что клик по внутреннему<p>
вызовет обработчикonclick
(если есть) сначала на самом<p>
, затем на элементе<div>,
далее на элементе<form>
и так далее вверх по цепочке родителей до самогоdocument
.
Поэтому если в примере выше кликнуть наP
, то последовательно выведутсяalert
:p
→div
→form
.
Этот процесс называется всплытием, потому что события «всплывают» от внутреннего элемента вверх через родителей.
Целевой элемент event.target
На каком бы элементе мы ни поймали событие, всегда можно узнать, где конкретно оно произошло.
Самый глубокий элемент, который вызывает событие, называется «целевым» или «исходным» элементом и доступен как event.target
.
Отличия отthis
(=event.currentTarget
):
event.target
– это исходный элемент, на котором произошло событие, в процессе всплытия он неизменен.this
– это текущий элемент, до которого дошло всплытие, на нем сейчас выполняется обработчик.
Например, если стоит только один обработчикform.onclick
, то он «поймает» все клики внутри формы. Где бы ни был клик внутри – он всплывет до элемента<form>
, на котором сработает обработчик.
При этом:
this
(=event.currentTarget
) всегда будет сама форма, так как обработчик сработал на ней.event.target
будет содержать ссылку на конкретный элемент внутри формы, самый вложенный, на котором произошел клик.
Возможна и ситуация, когдаevent.target
иthis
– один и тот же элемент, например, если в форме нет других тегов и клик был на самом элементе<form>
.
Прекращение всплытия
Всплытие идет прямо наверх. Обычно событие будет всплывать вверх и вверх, до элемента<html>
, а затем доdocument
, а иногда даже доwindow
, вызывая все обработчики на своем пути.
Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие.
Для остановки всплытия нужно вызвать методevent.stopPropagation()
.
Например, здесь при клике на кнопку обработчикbody.onclick
не сработает:
Погружение
В современном стандарте, кроме «всплытия» событий, предусмотрено еще и «погружение».
Оно гораздо менее востребовано, но иногда, очень редко, знание о нем может быть полезным.
Строго говоря, стандарт выделяет целых три стадии прохода события:
Событие сначала идет сверху вниз. Эта стадия называется
«стадия перехвата»(capturing stage).
Событие достигло целевого элемента. Это –«стадия цели»(target stage).
После этого событие начинает всплывать. Это –«стадия всплытия»(bubbling stage).
В стандарте DOM Events 3 это продемонстрировано так:
То есть, при клике наTD,
событие путешествует по цепочке родителей сначала вниз к элементу («погружается»), а потом наверх («всплывает»), задействуя обработчики.
Обработчики, добавленные черезon...
-свойство, ничего не знают о стадии перехвата, а начинают работать со всплытия.
Чтобы поймать событие на стадии перехвата, нужно использовать третий аргументaddEventListener
:
Если аргумент
true
, то событие будет перехвачено по дороге вниз.Если аргумент
false
, то событие будет поймано при всплытии.
Делегирование событий
Всплытие событий позволяет реализовать один из самых важных приемов разработки –делегирование.
Он заключается в том, что если у нас есть много элементов, события на которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому – мы ставим один обработчик на их общего предка. Из него можно получить целевой элементevent.target
, понять, на каком именно потомке произошло событие, и обработать его.
Last updated