События
- События: от браузерных до пользовательских» от Игоря Зубова
- Описание событий на 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>;