События
- События: от браузерных до пользовательских» от Игоря Зубова
- Описание событий на MDN
- preventDefault()
- stopPropagation()
- Всплытие и погружение
JS однопоточный. Очередь событий.
События в браузере
Для реакции на действия пользователя или внутреннее взаимодействие кода используют события — сигнал от браузера о том, что что-то произошло. События можно разделить на группы — в зависимости от устройства или элемента интерфейса, который дал им начало: События мыши
click
— клик левой кнопкой мыши;contextmenu
— клик правой кнопкой мыши;mouseover/mouseout
— курсор попадает на элемент или уходит с него;mousedown/mouseup
— кнопку мыши нажали или отпустили;mousemove
— движение курсора над элементом.
click, submit, dblclick, keydown, keyup, keypress, mouseover, mouseout, reset, focus, focusin, focusout, change, blur
События клавиатуры
keydown
— клавишу нажали;keyup
— клавишу отпустили.
el.addEventListener('keydown', function(e) {
// js for keypress;
if (e.keyCode == 27) { ... }
});
События при нажатии пальцем
touchstart
— элемента коснулись;touchmove
— по элементу провели пальцем;touchend
— касание закончилось и палец убрали;touchcancel
— палец переместился на интерфейс браузера или тач-событие нужно отменить.
el.addEventListener('click', (event) => {
// do smth
});
События на элементах управления
submit
— нажали кнопку «Отправить» формы <form>;reset
— сбросили форму <form>;focus
— пользователь фокусируется на элементе, например, нажимает на <input>;blur
— пользователь выходит из фокуса элемента, например, кликает вне <input>.
Больше событий описано на MDN.
// onchange
el.onchange = () => { ... }
// onsubmit
el.onsubmit = () => { ... }
// onload
<body onload="alert('Страница загружена');">
Подписки. Добавление обработчика
Обработчик (от англ. handler) — это функция, которая отрабатывает, как только произошло событие. Именно он позволяет JavaScript-коду моментально реагировать на действия пользователя.
Через атрибут
Передать обработчик можно напрямую через атрибут onclick. У такого способа много ограничений и неудобств. Использовать его не стоит, кроме случаев, где это действительно нужно.
<button onclick="this.parentElement.innerHTML+='<span>click</span>'">
Нажми меня
</button>
Через свойства DOM-элемента
У элементов есть свойства on*, в которых можно присваивать обработчики. Но нужно быть готовым к тому, что явное присваивание заменяет все прошлые обработчики. Этот способ лучше не использовать без особой необходимости.
let count = 0;
const element = document.getElementsByTagName('BUTTON')[0];
element.onclick = function() {
element.innerHTML = `Кликнули ${++count} раз`;
}
addEventListener и removeEventListener
Самый верный способ навесить и удалить обработчик. Настоятельно рекомендуем использовать по умолчанию именно эти два метода:
// Добавление обработчика
element.addEventListener(event, handler);
// Удаление обработчика
// Нужно передать те же аргументы, что были у addEventListener
element.removeEventListener(event, handler);
////////////////////////////////////////////////
// Вот такой способ не сработает
// (в аргументах хоть внешне и одинаковые, но по сути разные функции)
element.addEventListener(event, () => '');
element.removeEventListener(event, () => '');
// Вот такой способ сработает (в аргументах одна и та же функция)
const handler = () => '';
element.addEventListener(event, handler);
element.removeEventListener(event, handler);
Подробнее про addEventListener на MDN.
event
Обработчики принимают на вход объект события event. Он сообщает свойства элемента на момент реагирования, что изменилось и что добавилось. Например, какая клавиша нажата. То есть можно определять не только абстрактные общие вещи вроде «нажали что-то».
element.addEventListener('click', function (event) {
console.log(`${event.type} на ${event.currentTarget}`);
console.log(`${event.clientX}:${event.clientY}`);
});
У event
есть много разных свойств и методов. Подробный список найдёте на MDN.
Свойства, которые показаны в примере:
event.type
— тип события (в примере это 'click').event.currentTarget
— на каком элементе сработал обработчик. Не путайте с event.target, то есть исходным элементом, на котором произошло событие. Они могут быть разными из-за всплытия событий, о которых расскажем дальше.event.clientX / event.clientY
— свойства событий мыши, показывают координаты курсора относительно окна в момент клика.
Всплытие событий
Всплытие работает следующим образом. Сначала отрабатывают события на самом вложенном элементе, затем на его родителе, и так далее, вверх до window.
Например, возьмём форму. При нажатии на текст P будет показано три alert в следующем порядке:
- alert('p') ,
- alert('div'),
- alert('form').
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
Событие проходит следующие стадии:
- Перехват (capturing) — событие проходит сверху вниз.
- Цель (target) — событие достигло целевого элемента.
- Всплытие (bubbling) — событие идёт снизу вверх.
Самый глубокий элемент, который вызывает событие, называется целевым (target). Доступен из объекта события как event.target.
eventform.addEventListener('click', function(event) {
console.log('target = ', event.target.tagName); // target = FORM
console.log('this = ', this.tagName); // this = FORM
});
Работа с событием
event.preventDefault()
— отменяет обработчик по умолчанию,
Код ниже вызовет переход на другую страницу, потому что таково поведение формы по умолчанию:
<form onsubmit="alert('submit!');">
Первый пример: нажмите Enter: <input type="text" value="Текст"><br>
Второй пример: нажмите на кнопку "Отправить": <input type="submit" value="Отправить">
</form>
Но если к обработчику в addEventListener
добавить preventDefault
, то перехода на другую страницу не будет.
event.stopPropagation
— прекращает всплытие (например, при нажатии на кнопку не вызовет стандартное поведения <form>).event.stopImmediatePropagation
— прекращает всплытие и не выполняет оставшиеся обработчики события.
Перехват события
on*
— обработчики не обрабатывают перехват;element.addEventListener('event', callback, false)
— обработка на стадии всплытия (поведение по умолчанию);element.addEventListener('event', callback, true)
— обработка на стадии перехвата.
Делегирование событий
Рассмотрим пример со списком. Есть список, в котором много дочерних элементов:
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
Как можно навесить обработчик на все элементы?
const logger = function(event) {
console.log(event.target.innerHTML);
}
const liElements = document.getElementsByTagName('li');
for (let i = 0; i < liElements.length; i++) {
const li = liElements[i];
li.addEventListener('click', logger);
}
Можно сделать намного проще, используя знания о делегировании, всплытии и перехвате:
const logger = function(event) {
if(event.target.tagName === 'LI') {
console.log(event.target.innerHTML);
}
}
const ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('click', logger);
Преимущества данного подхода:
- один обработчик вместо множества;
- при добавлении новых элементов им не нужно добавлять обработчик.
onClick пример
Сработает только последний
btn.onclick = function () {
result.push('first event');
console.log(result);
}
btn.onclick = () => {
result.push('second event');
console.log(result);
};
btn.click();
Навешивание обработчика
const btn = document.querySelector('.button');
const result = [];
btn.addEventListener('click', function(event) {
result.push('first event');
result.push('second event');
console.log(result);
});
Event loop
Бесконечный цикл, который постоянно крутиться интерпретатором и улавливает все события.
После того, как отлавливает события, то записывает их в СТЕК(очередь) ЗАДАЧ.
Задачи выполняются АСИНХРОННО, это означает, что если какая-либо задача выполняется, то Event loop не прекращается свою работу, а будет улавливать все события постоянно.
Список событий
Event | Occurs When | Belongs To |
---|---|---|
abort | The loading of a media is aborted | UiEvent, Event |
afterprint | A page has started printing | Event |
animationend | A CSS animation has completed | AnimationEvent |
animationiteration | A CSS animation is repeated | AnimationEvent |
animationstart | A CSS animation has started | AnimationEvent |
beforeprint | A page is about to be printed | Event |
beforeunload | Before a document is about to be unloaded | UiEvent,Event |
blur | An element loses focus | FocusEvent |
canplay | The browser can start playing a media (has buffered enough to begin) | Event |
canplaythrough | The browser can play through a media without stopping for buffering | Event |
change | The content of a form element has changed | Event |
click | An element is clicked on | MouseEvent |
contextmenu | ПКМ: An element is right-clicked to open a context menu | MouseEvent |
copy | The content of an element is copied | ClipboardEvent |
cut | The content of an element is cut | ClipboardEvent |
dblclick | An element is double-clicked | MouseEvent |
drag | An element is being dragged | DragEvent |
dragend | Dragging of an element has ended | DragEvent |
dragenter | A dragged element enters the drop target | DragEvent |
dragleave | A dragged element leaves the drop target | DragEvent |
dragover | A dragged element is over the drop target | DragEvent |
dragstart | Dragging of an element has started | DragEvent |
drop | A dragged element is dropped on the target | DragEvent |
durationchange | The duration of a media is changed | Event |
ended | A media has reach the end ("thanks for listening") | Event |
error | An error has occurred while loading a file | ProgressEvent,UiEvent, Event |
focus | An element gets focus | FocusEvent |
focusin | An element is about to get focus | FocusEvent |
focusout | An element is about to lose focus | FocusEvent |
fullscreenchange | An element is displayed in fullscreen mode | Event |
fullscreenerror | An element can not be displayed in fullscreen mode | Event |
hashchange | There has been changes to the anchor part of a URL | HashChangeEvent |
input | An element gets user input | InputEvent,Event |
invalid | An element is invalid | Event |
keydown | A key is down | KeyboardEvent |
keypress | A key is pressed | KeyboardEvent |
keyup | A key is released | KeyboardEvent |
load | An object has loaded | UiEvent,Event |
loadeddata | Media data is loaded | Event |
loadedmetadata | Meta data (like dimensions and duration) are loaded | Event |
loadstart | The browser starts looking for the specified media | ProgressEvent |
message | A message is received through the event source | Event |
mousedown | The mouse button is pressed over an element | MouseEvent |
mouseenter | The pointer is moved onto an element | MouseEvent |
mouseleave | The pointer is moved out of an element | MouseEvent |
mousemove | The pointer is moved over an element | MouseEvent |
mouseover | The pointer is moved onto an element | MouseEvent |
mouseout | The pointer is moved out of an element | MouseEvent |
mouseup | A user releases a mouse button over an element | MouseEvent |
mousewheel | Deprecated. Use thewheel event instead | WheelEvent |
offline | The browser starts working offline | Event |
online | The browser starts working online | Event |
open | A connection with the event source is opened | Event |
pagehide | User navigates away from a webpage | PageTransitionEvent |
pageshow | User navigates to a webpage | PageTransitionEvent |
paste | Some content is pasted in an element | ClipboardEvent |
pause | A media is paused | Event |
play | The media has started or is no longer paused | Event |
playing | The media is playing after being paused or buffered | Event |
popstate | The window's history changes | PopStateEvent |
progress | The browser is downloading media data | Event |
ratechange | The playing speed of a media is changed | Event |
resize | The document view is resized | UiEvent,Event |
reset | A form is reset | Event |
scroll | An scrollbar is being scrolled | UiEvent,Event |
search | Something is written in a search field | Event |
seeked | Skipping to a media position is finished | Event |
seeking | Skipping to a media position is started | Event |
select | User selects some text | UiEvent,Event |
show | A <menu> element is shown as a context menu | Event |
stalled | The browser is trying to get unavailable media data | Event |
storage | A Web Storage area is updated | StorageEvent |
submit | A form is submitted | Event |
suspend | The browser is intentionally not getting media data | Event |
timeupdate | The playing position has changed (the user moves to a different point in the media) | Event |
toggle | The user opens or closes the <details> element | Event |
touchcancel | The touch is interrupted | TouchEvent |
touchend | A finger is removed from a touch screen | TouchEvent |
touchmove | A finger is dragged across the screen | TouchEvent |
touchstart | A finger is placed on a touch screen | TouchEvent |
transitionend | A CSS transition has completed | TransitionEvent |
unload | A page has unloaded | UiEvent,Event |
volumechange | The volume of a media is changed (includes muting) | Event |
waiting | A media is paused but is expected to resume (e.g. buffering) | Event |
wheel | The mouse wheel rolls up or down over an element | WheelEvent |
Scroll, прокрутка до элемента
import { useEffect, useRef } from 'react';
const ref = useRef<HTMLDivElement>(null);
// Скроллинг до этого элемента, если он был последний просмотренный
useEffect(() => {
if (isLastEdited && ref.current) {
ref.current.scrollIntoView({ behavior: 'smooth' });
}
}, [isLastEdited]);
<div ref={ref}>
...
</div>