503 lines
No EOL
21 KiB
Markdown
503 lines
No EOL
21 KiB
Markdown
# Введение в браузерные события
|
||
|
||
Для реакции на действия посетителя и внутреннего взаимодействия скриптов существуют *события*.
|
||
|
||
[cut]
|
||
|
||
*Событие* -- это сигнал от браузера о том, что что-то произошло. Существует много видов событий. Посмотрим список самых часто используемых, пока просто для ознакомления:
|
||
|
||
<dl>
|
||
<dt>События мыши</dt>
|
||
<dd>
|
||
<ul>
|
||
<li>`click` -- происходит, когда кликнули на элемент левой кнопкой мыши</li>
|
||
<li>`contextmenu` -- происходит, когда кликнули на элемент правой кнопкой мыши</li>
|
||
<li>`mouseover` -- возникает, когда на элемент наводится мышь</li>
|
||
<li>`mousedown` и `mouseup` -- когда кнопку мыши нажали или отжали</li>
|
||
<li>`mousemove` -- при движении мыши</li>
|
||
</ul>
|
||
</dd>
|
||
<dt>События на элементах управления</dt>
|
||
<dd>
|
||
<ul>
|
||
<li>`submit` -- посетитель отправил форму `<form>`</li>
|
||
<li>`focus` -- посетитель фокусируется на элементе, например нажимает на `<input>`</li>
|
||
</ul>
|
||
<dt>Клавиатурные события</dt>
|
||
<dd>
|
||
<ul>
|
||
<li>`keydown` -- когда посетитель нажимает клавишу</li>
|
||
<li>`keyup` -- когда посетитель отпускает клавишу</li>
|
||
</ul>
|
||
</dd>
|
||
<dt>События документа</dt>
|
||
<dd>
|
||
<ul>
|
||
<li>`DOMContentLoaded` -- когда HTML загружен и обработан, DOM документа полностью построен и доступен.</li>
|
||
</ul></dd>
|
||
<dt>События CSS</dt>
|
||
<dd>
|
||
<ul>
|
||
<li>`transitionend` -- когда CSS-анимация завершена.</li>
|
||
</ul></dd>
|
||
</dl>
|
||
|
||
Также есть и много других событий.
|
||
|
||
## Назначение обработчиков событий
|
||
|
||
Событию можно назначить *обработчик*, то есть функцию, которая сработает, как только событие произошло.
|
||
|
||
Именно благодаря обработчикам JavaScript-код может реагировать на действия посетителя.
|
||
|
||
Есть несколько способов назначить событию обработчик. Сейчас мы их рассмотрим, начиная от самого простого.
|
||
|
||
### Использование атрибута HTML
|
||
|
||
Обработчик может быть назначен прямо в разметке, в атрибуте, который называется `on<событие>`.
|
||
|
||
Например, чтобы прикрепить `click`-событие к `input` кнопке, можно присвоить обработчик `onclick`, вот так:
|
||
|
||
```html
|
||
<input value="Нажми меня" *!*onclick="alert('Клик!')"*/!* type="button">
|
||
```
|
||
|
||
При клике мышкой на кнопке выполнится код, указанный в атрибуте `onclick`.
|
||
|
||
[online]
|
||
В действии: <input value="Нажми меня" onclick="alert('Клик!');" type="button">
|
||
[/online]
|
||
|
||
Обратите внимание, для содержимого атрибута `onclick` используются *одинарные кавычки*, так как сам атрибут находится в двойных.
|
||
|
||
Частая ошибка новичков в том, что они забывают, что код находится внутри атрибута. Запись вида `onclick="alert("Клик!")"`, с двойными кавычки внутри, не будет работать. Если вам действительно нужно использовать именно двойные кавычки, то это можно сделать, заменив их на `"`, то есть так: <code>onclick="alert(&quot;Клик!&quot;)"</code>.
|
||
|
||
Однако, обычно этого не требуется, так как прямо в разметке пишутся только очень простые обработчики. Если нужно сделать что-то сложное, то имеет смысл описать это в функции, и в обработчике вызвать уже её.
|
||
|
||
Следующий пример по клику запускает функцию `countRabbits()`.
|
||
|
||
```html
|
||
<!--+ run height=80 -->
|
||
<!DOCTYPE HTML>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
|
||
<script>
|
||
function countRabbits() {
|
||
for(var i=1; i<=3; i++) {
|
||
alert("Кролик номер " + i);
|
||
}
|
||
}
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<input type="button" *!*onclick="countRabbits()"*/!* value="Считать кроликов!"/>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
Как мы помним, атрибут HTML-тега не чувствителен к регистру, поэтому `ONCLICK` будет работать так же, как `onClick` или `onCLICK`... Но, как правило, атрибуты пишут в нижнем регистре: `onclick`.
|
||
|
||
### Использование свойства DOM-объекта
|
||
|
||
Можно назначать обработчик, используя свойство DOM-элемента `on<событие>`.
|
||
|
||
Пример установки обработчика `click`:
|
||
|
||
```html
|
||
<input id="elem" type="button" value="Нажми меня" />
|
||
<script>
|
||
*!*
|
||
elem.onclick = function() {
|
||
alert( 'Спасибо' );
|
||
};
|
||
*/!*
|
||
</script>
|
||
```
|
||
|
||
Если обработчик задан через атрибут, то браузер читает HTML-разметку, создаёт новую функцию из содержимого атрибута и записывает в свойство `onclick`.
|
||
|
||
**Этот способ, по сути, аналогичен предыдущему.**
|
||
|
||
Обработчик хранится именно в DOM-свойстве, а атрибут -- лишь один из способов его инициализации.
|
||
|
||
Эти два примера кода работают одинаково:
|
||
|
||
<ol>
|
||
<li>Только HTML:
|
||
|
||
```html
|
||
<!--+ run height=50 -->
|
||
<input type="button" *!*onclick="alert('Клик!')"*/!* value="Кнопка"/>
|
||
```
|
||
|
||
</li>
|
||
<li>HTML + JS:
|
||
|
||
```html
|
||
<!--+ run height=50 -->
|
||
<input type="button" id="button" value="Кнопка" />
|
||
<script>
|
||
*!*
|
||
button.onclick = function() {
|
||
alert( 'Клик!' );
|
||
};
|
||
*/!*
|
||
</script>
|
||
```
|
||
|
||
</li>
|
||
</ol>
|
||
|
||
**Так как DOM-свойство `onclick`, в итоге, одно, то назначить более одного обработчика так нельзя.**
|
||
|
||
В примере ниже назначение через JavaScript перезапишет обработчик из атрибута:
|
||
|
||
```html
|
||
<!--+ run height=50 autorun -->
|
||
<input type="button" id="elem" onclick="alert('До')" value="Нажми меня" />
|
||
<script>
|
||
*!*
|
||
elem.onclick = function() { // перезапишет существующий обработчик
|
||
alert( 'После' ); // выведется только это
|
||
};
|
||
*/!*
|
||
</script>
|
||
```
|
||
|
||
Кстати, обработчиком можно назначить и уже существующую функцию:
|
||
|
||
```js
|
||
function sayThanks() {
|
||
alert( 'Спасибо!' );
|
||
}
|
||
|
||
elem.onclick = sayThanks;
|
||
```
|
||
|
||
Если обработчик надоел -- его всегда можно убрать назначением `elem.onclick = null`.
|
||
|
||
## Доступ к элементу через this
|
||
|
||
Внутри обработчика события `this` ссылается на текущий элемент, то есть на тот, на котором он сработал.
|
||
|
||
Это можно использовать, чтобы получить свойства или изменить элемент.
|
||
|
||
В коде ниже `button` выводит свое содержимое, используя `this.innerHTML`:
|
||
|
||
```html
|
||
<button onclick="alert(this.innerHTML)">Нажми меня</button>
|
||
```
|
||
|
||
[online]
|
||
В действии: <button onclick="alert(this.innerHTML)">Нажми меня</button>
|
||
[/online]
|
||
|
||
## Частые ошибки
|
||
|
||
Если вы только начинаете работать с событиями -- обратите внимание на следующие особенности.
|
||
|
||
<dl>
|
||
<dt>Функция должна быть присвоена как `sayThanks`, а не `sayThanks()`.</dt>
|
||
<dd>
|
||
|
||
```js
|
||
button.onclick = sayThanks;
|
||
```
|
||
|
||
Если добавить скобки, то `sayThanks()` -- будет уже *результат* выполнения функции (а так как в ней нет `return`, то в `onclick` попадёт `undefined`). Нам же нужна именно функция.
|
||
|
||
...А вот в разметке как раз скобки нужны:
|
||
|
||
```html
|
||
<input type="button" id="button" onclick="sayThanks()" />
|
||
```
|
||
|
||
Это различие просто объяснить. При создании обработчика браузером из атрибута, он автоматически создает функцию из его содержимого. Поэтому последний пример -- фактически то же самое, что:
|
||
|
||
```js
|
||
button.onclick = function() {
|
||
*!*
|
||
sayThanks(); // содержимое атрибута
|
||
*/!*
|
||
};
|
||
```
|
||
|
||
</dd>
|
||
<dt>Используйте именно функции, а не строки.</dt>
|
||
<dd>
|
||
Назначение обработчика строкой `elem.onclick = "alert(1)"` можно иногда увидеть в древнем коде. Это будет работать, но не рекомендуется, могут быть проблемы при сжатии JavaScript Да и вообще, передавать код в виде строки по меньшей мере странно в языке, который поддерживает Function Expressions. Это возможно лишь по соображениям совместимости, не делайте так.
|
||
</dd>
|
||
<dt>Не используйте `setAttribute`.</dt>
|
||
<dd>
|
||
Такой вызов работать не будет:
|
||
|
||
```js
|
||
//+ run no-beautify
|
||
// при нажатии на body будут ошибки
|
||
// потому что при назначении в атрибут функция будет преобразована в строку
|
||
document.body.setAttribute('onclick', function() { alert(1) });
|
||
```
|
||
|
||
</dd>
|
||
<dt>Регистр DOM-свойства имеет значение.</dt>
|
||
<dd>При назначении через DOM нужно использовать свойство `onclick`, а не `ONCLICK`.</dd>
|
||
</dl>
|
||
|
||
|
||
## Недостаток назначения через свойство
|
||
|
||
Фундаментальный недостаток описанных выше способов назначения обработчика -- невозможность повесить *несколько* обработчиков на одно событие.
|
||
|
||
Например, одна часть кода хочет при клике на кнопку делать ее подсвеченной, а другая -- выдавать сообщение. Нужно в разных местах два обработчика повесить.
|
||
|
||
При этом новый обработчик будет затирать предыдущий. Например, следующий код на самом деле назначает один обработчик -- последний:
|
||
|
||
```js
|
||
//+ no-beautify
|
||
input.onclick = function() { alert(1); }
|
||
// ...
|
||
input.onclick = function() { alert(2); } // заменит предыдущий обработчик
|
||
```
|
||
|
||
Разработчики стандартов достаточно давно это поняли и предложили альтернативный способ назначения обработчиков при помощи специальных методов, которые свободны от указанного недостатка.
|
||
|
||
## addEventListener и removeEventListener
|
||
|
||
Методы `addEventListener` и `removeEventListener` являются современным способом назначить или удалить обработчик, и при этом позволяют использовать сколько угодно любых обработчиков.
|
||
|
||
Назначение обработчика осуществляется вызовом `addEventListener` с тремя аргументами:
|
||
|
||
```js
|
||
element.addEventListener(event, handler[, phase]);
|
||
```
|
||
|
||
<dl>
|
||
<dt>`event`</dt>
|
||
<dd>Имя события, например `click`</dd>
|
||
<dt>`handler`</dt>
|
||
<dd>Ссылка на функцию, которую надо поставить обработчиком.</dd>
|
||
<dt>`phase`</dt>
|
||
<dd>Необязательный аргумент, "фаза", на которой обработчик должен сработать. Этот аргумент редко нужен, мы его рассмотрим позже.</dd>
|
||
</dl>
|
||
|
||
Удаление обработчика осуществляется вызовом `removeEventListener`:
|
||
|
||
```js
|
||
// передать те же аргументы, что были у addEventListener
|
||
element.removeEventListener(event, handler[, phase]);
|
||
```
|
||
|
||
[warn header="Удаление требует именно ту же функцию"]
|
||
Для удаления нужно передать именно ту функцию-обработчик которая была назначена.
|
||
|
||
Вот так `removeEventListener` не сработает:
|
||
|
||
```js
|
||
//+ no-beautify
|
||
elem.addEventListener( "click" , function() {alert('Спасибо!')});
|
||
// ....
|
||
elem.removeEventListener( "click", function() {alert('Спасибо!')});
|
||
```
|
||
|
||
В `removeEventListener` передана не та же функция, а другая, с одинаковым кодом, но это не важно.
|
||
|
||
Вот так правильно:
|
||
|
||
```js
|
||
function handler() {
|
||
alert( 'Спасибо!' );
|
||
}
|
||
|
||
input.addEventListener("click", handler);
|
||
// ....
|
||
input.removeEventListener("click", handler);
|
||
```
|
||
|
||
Обратим внимание -- если функцию не сохранить где-либо, а просто передать в `addEventListener`, как в предыдущем коде, то потом получить её обратно, чтобы снять обработчик, будет невозможно. Нет метода, который позволяет считать обработчики событий, назначенные через `addEventListener`.
|
||
[/warn]
|
||
|
||
Метод `addEventListener` позволяет добавлять несколько обработчиков на одно событие одного элемента, например:
|
||
|
||
```html
|
||
<!--+ run no-beautify -->
|
||
<input id="elem" type="button" value="Нажми меня"/>
|
||
|
||
<script>
|
||
function handler1() {
|
||
alert('Спасибо!');
|
||
};
|
||
|
||
function handler2() {
|
||
alert('Спасибо ещё раз!');
|
||
}
|
||
|
||
*!*
|
||
elem.onclick = function() { alert("Привет"); };
|
||
elem.addEventListener("click", handler1); // Спасибо!
|
||
elem.addEventListener("click", handler2); // Спасибо ещё раз!
|
||
*/!*
|
||
</script>
|
||
```
|
||
|
||
Как видно из примера выше, можно одновременно назначать обработчики и через DOM-свойство и через `addEventListener`. Однако, во избежание путаницы, рекомендуется выбрать один способ.
|
||
|
||
[warn header="`addEventListener` работает всегда, а DOM-свойство -- нет"]
|
||
У специальных методов есть ещё одно перимущество перед DOM-свойствами.
|
||
|
||
Есть некоторые события, которые нельзя назначить через DOM-свойство, но можно через `addEventListener`.
|
||
|
||
Например, таково событие `transitionend`, то есть окончание CSS-анимации. В большинстве браузеров оно требует назначения через `addEventListener`.
|
||
|
||
Вы можете проверить это, запустив код в примере ниже. Как правило, сработает лишь второй обработчик, но не первый.
|
||
|
||
```html
|
||
<!--+ run -->
|
||
<style>
|
||
button {
|
||
transition: width 1s;
|
||
width: 100px;
|
||
}
|
||
|
||
.wide {
|
||
width: 300px;
|
||
}
|
||
</style>
|
||
|
||
<button id="elem" onclick="this.classList.toggle('wide');">
|
||
Нажми меня
|
||
</button>
|
||
|
||
<script>
|
||
elem.ontransitionend = function() {
|
||
alert( "ontransitionend" ); // не сработает
|
||
};
|
||
|
||
*!*
|
||
elem.addEventListener("transitionend", function() {
|
||
alert( "addEventListener" ); // сработает по окончании анимации
|
||
});
|
||
*/!*
|
||
</script>
|
||
```
|
||
|
||
[/warn]
|
||
|
||
## Отличия IE8-
|
||
|
||
При работе с событиями в IE8- есть много отличий. Как правило, они формальны -- некое свойство или метод называются по-другому. Начиная с версии 9, также работают и стандартные свойства и методы.
|
||
|
||
**В IE8- вместо `addEventListener/removeEventListener` используются свои методы.**
|
||
|
||
Назначение обработчика осуществляется вызовом `attachEvent`:
|
||
|
||
```js
|
||
element.attachEvent("on" + event, handler);
|
||
```
|
||
|
||
Удаление обработчика -- вызовом `detachEvent`:
|
||
|
||
```js
|
||
element.detachEvent("on" + event, handler);
|
||
```
|
||
|
||
Например:
|
||
|
||
```js
|
||
function handler() {
|
||
alert( 'Спасибо!' );
|
||
}
|
||
button.attachEvent("onclick", handler) // Назначение обработчика
|
||
// ....
|
||
button.detachEvent("onclick", handler) // Удаление обработчика
|
||
```
|
||
|
||
Как видите, почти то же самое, только событие должно включать префикс `on`.
|
||
|
||
[warn header="У обработчиков, назначенных с `attachEvent`, нет `this`"]
|
||
Обработчики, назначенные с `attachEvent` не получают `this`!
|
||
|
||
Это важная особенность и подводный камень старых IE.
|
||
[/warn]
|
||
|
||
Чтобы ваш код работал в старом IE, нужно либо использовать DOM-свойства, то есть `onclick`, либо подключить полифилл для современных методов, например [такой](https://gist.github.com/jonathantneal/3748027) или с сервиса [polyfill.io](http://polyfill.webservices.ft.com/v1/docs/features/) или какой-то другой.
|
||
|
||
|
||
## Итого
|
||
|
||
Есть три способа назначения обработчиков событий:
|
||
|
||
<ol>
|
||
<li>Атрибут HTML: `onclick="..."`.</li>
|
||
<li>Свойство: <code>elem.onclick = function</code>.</li>
|
||
<li>Специальные методы:
|
||
<ul>
|
||
<li>Современные: `elem.addEventListener( событие, handler[, phase])`, удаление через `removeEventListener`.</li>
|
||
<li>Для старых IE8-: `elem.attachEvent( on+событие, handler )`, удаление через `detachEvent`.</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
|
||
Сравнение `addEventListener` и `onclick`:
|
||
[compare]
|
||
+Некоторые события можно назначить только через `addEventListener`.
|
||
+Метод `addEventListener` позволяет назначить много обработчиков на одно событие.
|
||
-Обработчик, назначенный через `onclick`, проще удалить или заменить.
|
||
-Метод `onclick` кросс-браузерный.
|
||
[/compare]
|
||
|
||
Этим введением мы только открывает работу с событиями, но вы уже можете решать разнообразные задачи с их использованием.
|
||
|
||
|
||
[head]
|
||
<style type="text/css">
|
||
.d0 { text-align:center;margin:auto; }
|
||
.d1 p { margin: 0 }
|
||
.d1 {
|
||
margin:2em;
|
||
background-color:green;
|
||
width:13em;
|
||
height:13em;
|
||
text-align:center;
|
||
}
|
||
.d1 .number {
|
||
line-height: 2em;
|
||
}
|
||
.d2 {
|
||
text-align:center;
|
||
margin:auto;
|
||
background-color:blue;
|
||
width:9em;
|
||
height:9em;
|
||
}
|
||
.d1 .d2 ,number {
|
||
line-height: 2em;
|
||
}
|
||
.d3 {
|
||
text-align:center;
|
||
margin:auto;
|
||
background-color:red;
|
||
width:5em;
|
||
height:5em;
|
||
}
|
||
.d1 .d2 .d3 .number {
|
||
line-height: 5em;
|
||
}
|
||
.d1 .d2 .d2a {
|
||
color:white;
|
||
line-height: 2em;
|
||
}
|
||
</style>
|
||
<script type="text/javascript">
|
||
function highlightMe(elem) {
|
||
elem.style.backgroundColor='yellow'
|
||
alert(elem.className)
|
||
elem.style.backgroundColor = ''
|
||
}
|
||
|
||
function highlightMe2(e) {
|
||
highlightMe(e.currentTarget);
|
||
}
|
||
</script>
|
||
[/head] |