Подія – це сигнал від браузера, що щось сталося. Всі DOM-вузли подають такі сигнали (хоча події бувають не тільки в DOM).
Ось список найпоширеніших DOM-подій, поки що просто для ознайомлення:
Події миші:
click
– відбувається, коли клацнули на елемент лівою кнопкою миші (на пристроях із сенсорними екранами воно відбувається при торканні).contextmenu
– відбувається, коли клацнули на елемент правою кнопкою миші.mouseover
/mouseout
– коли миша наводиться на / залишає елемент.mousedown
/mouseup
– коли натиснули / відпустили кнопку миші на елементі.mousemove
– під час руху миші.
Події клавіатури:
keydown
таkeyup
– коли користувач натискає / відпускає клавішу.
Події елементів форми:
submit
– користувач надіслав форму<form>
.focus
– користувач фокусується на елементі, наприклад, натискає на<input>
.
Події документа:
DOMContentLoaded
– коли HTML завантажено й оброблено, DOM документа повністю побудований і доступний.
CSS події:
transitionend
– коли CSS-анімацію завершено.
Існує багато інших подій. Ми докладно розберемо їх у наступних розділах.
Обробники подій
Події можна призначити обробник, тобто функцію, яка спрацює, щойно подія сталася.
Саме завдяки обробникам JavaScript код може реагувати на дії користувача.
Є кілька способів призначити події обробник. Зараз ми їх розглянемо, починаючи з найпростішого.
Використання атрибута HTML
Обробник може бути призначений прямо в розмітці, атрибуті, який називається on<event>
.
Наприклад, щоб призначити обробник події click
на елементі input
, можна використовувати атрибут onclick
, ось так:
<input value="Натисни мене" onclick="alert('Клік!')" type="button">
При натисканні мишкою на кнопці виконається код, вказаний в атрибуті onclick
.
Зверніть увагу, що для вмісту атрибуту onclick
використовуються одинарні лапки, а сам атрибут знаходиться в подвійних. Якщо ми забудемо про це і поставимо подвійні лапки всередині атрибуту, ось так: onclick="alert("Click!")"
, код не буде працювати.
Атрибут HTML-тега – не найзручніше місце для написання великої кількості коду, тому краще створити окрему JavaScript-функцію та викликати її там.
Наступний приклад по кліку запускає функцію countRabbits()
:
<script>
function countRabbits() {
for(let i=1; i<=3; i++) {
alert("Кролик номер " + i);
}
}
</script>
<input type="button" onclick="countRabbits()" value="Рахувати кроликів!">
Як ми пам’ятаємо, атрибут HTML-тега не чутливий до регістру, тому ONCLICK
буде працювати так само як onClick
і onCLICK
… Але, як правило, атрибути пишуть у нижньому регістрі onclick
.
Використання властивостей DOM-об’єкта
Можемо призначати обробник, використовуючи властивість DOM-елемента on<event>
.
Наприклад, elem.onclick
:
<input id="elem" type="button" value="Click me">
<script>
elem.onclick = function() {
alert('Дякую');
};
</script>
Якщо обробник заданий через атрибут, то браузер читає HTML-розмітку, створює нову функцію із вмісту атрибута та записує у властивість.
Цей спосіб, по суті, аналогічний до попереднього.
Ці два приклади коду працюють однаково:
-
Тільки HTML:
<input type="button" onclick="alert('Клік!')" value="Кнопка">
-
HTML + JS:
<input type="button" id="button" value="Кнопка"> <script> button.onclick = function() { alert('Клік!'); }; </script>
У першому прикладі використовувався атрибут HTML для ініціалізації button.onclick
, тоді як у другому прикладі – сценарій, ось і вся різниця.
Оскільки в елемента DOM може бути тільки одна властивість з ім’ям onclick
, то призначити більше одного обробника таким чином не можна.
У прикладі нижче призначення обробника додатково через JavaScript перезапише обробник з атрибуту:
<input type="button" id="elem" onclick="alert('Було')" value="Click me">
<script>
elem.onclick = function() { // Перезапише існуючий обробник
alert('Стало'); // виведеться лише це повідомлення
};
</script>
Щоб видалити обробник – установіть elem.onclick = null
.
Доступ до елемента через this
Усередині обробника події this
посилається на поточний елемент, тобто на той, на якому, як кажуть, «висить» (тобто призначений) обробник.
У коді нижче button
виводить свій вміст, використовуючи this.innerHTML
:
<button onclick="alert(this.innerHTML)">Натисни на мене</button>
Поширені помилки
Якщо ви починаєте працювати з подіями, зверніть увагу на наступні моменти.
Ви можете призначити функцію що вже визначена, як обробника:
function sayThanks() {
alert('Thanks!');
}
elem.onclick = sayThanks;
Будьте обережні! Функція повинна буди присвоєна в такому вигляді – sayThanks
, а не sayThanks()
.
// правильно
button.onclick = sayThanks;
// неправильно
button.onclick = sayThanks();
Якщо додати дужки, то sayThanks() – це вже виклик функції, результат якого (рівний undefined, тому що функція нічого не повертає) буде присвоєно onclick. Тож це не буде працювати.
…А ось у розмітці, на відміну від властивості, дужки потрібні:
<input type="button" id="button" onclick="sayThanks()">
Цю різницю просто пояснити. При створенні обробника браузером з атрибута він автоматично створює функцію з тілом зі значення атрибута:
Таким чином розмітка генерує таку властивість:
button.onclick = function() {
sayThanks(); // <-- вміст атрибуту
};
Не використовуйте setAttribute
для обробників.
Такий виклик не буде працювати:
// При натисканні на <body> виникнуть помилки,
// атрибути завжди рядки, і функція стане рядком
document.body.setAttribute('onclick', function() { alert(1) });
Регістр DOM-властивості має значення…
Використовуйте elem.onclick
, а не elem.ONCLICK
, тому що DOM-властивості чутливі до регістру.
addEventListener
Фундаментальний недолік описаних вище способів присвоєння обробника – неможливість повісити кілька обробників для однієї події.
Наприклад, одна частина коду хоче при натисканні на кнопку підсвітити її, а інша – показати повідомлення.
Ми хочемо призначити два обробники для цього. Але новий обробник перезапише попередній:
input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // замінить попередній обробник
Розробники стандартів досить давно це зрозуміли і запропонували альтернативний спосіб призначення обробників за допомогою спеціальних методів addEventListener
та removeEventListener
. Вони вільні від цього недоліку.
Синтаксис додавання обробника:
element.addEventListener(event, handler, [options]);
event
- Назва події, наприклад
"click"
. handler
- Посилання на функцію-обробник.
options
- Додатковий об’єкт із властивостями:
once
: якщоtrue
, тоді обробник буде автоматично вилучений після виконання.capture
: фаза, на якій повинен спрацювати обробник, докладніше про це буде розказано у розділі Бульбашковий механізм (спливання та занурення). Так історично склалося, щоoptions
може бутиfalse/true
, це те саме, що{capture: false/true}
.passive
: якщоtrue
, тоді обробник ніколи не викличеpreventDefault()
, докладніше про це буде розказано у розділі Типові дії браузера.
Для видалення обробника слід використовувати removeEventListener
:
element.removeEventListener(event, handler, [options]);
Для видалення потрібно передати саме ту функцію-обробник, яка була присвоєна.
Отак не спрацює:
elem.addEventListener( "click" , () => alert('Дякую!'));
// ....
elem.removeEventListener( "click", () => alert('Дякую!'));
Обробник не буде видалено, так як removeEventListener
передано не таку ж функцію, а іншу, з однаковим кодом.
Ось так вірно:
function handler() {
alert( 'Дякую!' );
}
input.addEventListener("click", handler);
// ....
input.removeEventListener("click", handler);
Зверніть увагу – якщо функцію обробник не зберегти будь-де, ми не зможемо її видалити. Немає методу, який дозволяє отримати з елемента обробники подій, присвоєні через addEventListener
.
Метод addEventListener
дозволяє додавати кілька обробників на одну подію одного елемента, наприклад:
<input id="elem" type="button" value="Натисни мене"/>
<script>
function handler1() {
alert('Дякую!');
};
function handler2() {
alert('Ще раз дякую!');
}
elem.onclick = () => alert("Привіт");
elem.addEventListener("click", handler1); // Дякую!
elem.addEventListener("click", handler2); // Ще раз дякую!
</script>
Як видно з прикладу вище, можна одночасно призначати обробники через DOM-властивість і через addEventListener
. Однак, щоб уникнути плутанини, рекомендується вибрати один спосіб.
addEventListener
Існують події, які не можна призначити через DOM-властивість, але можна через addEventListener
.
Наприклад, така подія DOMContentLoaded
, яке спрацьовує, коли завершено завантаження та побудову DOM документа.
// не буде працювати
document.onDOMContentLoaded = function() {
alert("DOM побудований");
};
// буде працювати
document.addEventListener("DOMContentLoaded", function() {
alert("DOM побудований");
});
Таким чином addEventListener
більш універсальний. Хоча зауважимо, що таких подій меншість, це скоріше виняток, ніж правило.
Об’єкт події
Щоб правильно обробити подію, можуть знадобитися деталі того, що сталося. Не просто “клік” або “натискання клавіші”, але й координати вказівника миші, яка саме клавіша натиснута і так далі.
Коли відбувається подія, браузер створює об’єкт події, записує в нього деталі та передає його як аргумент функції-обробнику.
Приклад нижче демонструє отримання координат миші з події:
<input type="button" value="Натисни мене" id="elem">
<script>
elem.onclick = function(event) {
// вивести тип події, елемент та координати кліка
alert(event.type + " на " + event.currentTarget);
alert("Координати: " + event.clientX + ":" + event.clientY);
};
</script>
Деякі властивості об’єкту event
:
event.type
- Тип події, у цьому випадку
"click"
. event.currentTarget
- Елемент, у якому спрацював обробник. Це значення зазвичай таке саме, як і в
this
, але якщо обробник є функцією-стрілкою чи за допомогоюbind
прив’язаний інший об’єктthis
, то ми можемо отримати елемент зevent.currentTarget
. event.clientX / event.clientY
- Координати курсору в момент кліку у площині вікна для подій миші.
Існує багато властивостей подій. Більшість з них залежать від типу події: події клавіатури мають один набір подій, а події вказівника – інший. Ми розглянемо детальніше деякі з них пізніше.
При призначенні обробника в HTML теж можна використовувати об’єкт event
, ось так:
<input type="button" onclick="alert(event.type)" value="Тип події">
Це можливо тому, що коли браузер з атрибуту створює функцію-обробник, вона виглядає так: function(event) { alert(event.type) }
. Тобто, її перший аргумент називається "event"
, а тіло взяте з атрибуту.
Об’єкт-обробник: handleEvent
Ми можемо призначити обробником не лише функцію, а й об’єкт за допомогою addEventListener
. У такому разі, коли відбувається подія, викликається метод об’єкта handleEvent
.
Наприклад:
<button id="elem">Натисни мене</button>
<script>
let obj = {
handleEvent(event) {
alert(event.type + " at " + event.currentTarget);
}
};
elem.addEventListener('click', obj);
</script>
Як бачимо, якщо addEventListener
отримує об’єкт як обробник, він викликає obj.handleEvent(event)
, коли відбувається подія.
Ми також можемо використати клас для цього, ось так:
<button id="elem">Натисни мене</button>
<script>
class Menu {
handleEvent(event) {
switch(event.type) {
case 'mousedown':
elem.innerHTML = "Натиснута кнопка миші";
break;
case 'mouseup':
elem.innerHTML += "...і відпущена.";
break;
}
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
Тут той самий об’єкт обробляє обидві події. Зверніть увагу, ми повинні явно призначити обидва обробники через addEventListener
. Тоді об’єкт menu
отримуватиме події mousedown
та mouseup
, а не інші (не призначені) типи подій.
Метод handleEvent
не обов’язково має виконувати всю роботу сам. Він може викликати інші методи, які створені під обробку конкретних типів подій, ось так:
<button id="elem">Натисни мене</button>
<script>
class Menu {
handleEvent(event) {
// mousedown -> onMousedown
let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1);
this[method](event);
}
onMousedown() {
elem.innerHTML = "Натиснута кнопка миші";
}
onMouseup() {
elem.innerHTML += "...і відпущена.";
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
Тепер обробка подій розділена методами, що спрощує підтримку коду.
Підсумки
Є три способи призначення обробників подій:
- Атрибут HTML:
onclick="..."
. - Властивість DOM:
elem.onclick = function
. - Спеціальні методи:
elem.addEventListener(event, handler[, phase])
для додавання,removeEventListener
для видалення.
HTML-атрибути використовуються рідко тому, що JavaScript у HTML-тегу виглядає трохи дивно. До того ж багато коду там не напишеш.
DOM-властивості можна використовувати, але ми не можемо призначити більше одного обробника на один тип події. У багатьох випадках із цим обмеженням можна миритися.
Останній спосіб найбільш гнучкий, проте потрібно писати більше коду. Є кілька типів подій, які працюють лише через нього, наприклад transitionend
та DOMContentLoaded
. Також addEventListener
підтримує об’єкти-обробники подій. В цьому випадку викликається метод об’єкту handleEvent
.
Не важливо, як ви призначаєте обробник, він отримує об’єкт події першим аргументом. Цей об’єкт містить подробиці про те, що сталося.
Ми вивчимо більше про події та їх типи у наступних розділах.