en.javascript.info/02-ui/02-events-and-interfaces/01-introduction-browser-events/article.md
Ilya Kantor f301cb744d init
2014-10-26 22:10:13 +03:00

21 KiB
Raw Blame History

Введение в браузерные события

Для реакции на действия посетителя и внутреннего взаимодействия скриптов существуют события.

Событие - это сигнал от браузера о том, что что-то произошло. [cut] Существует много видов событий.

Посмотрим список самых часто используемых, пока просто для ознакомления:

События мыши
  • `click` -- происходит, когда кликнули на элемент левой кнопкой мыши
  • `contextmenu` -- происходит, когда кликнули на элемент правой кнопкой мыши
  • `mouseover` -- возникает, когда на элемент наводится мышь
  • `mousedown` и `mouseup` -- когда кнопку мыши нажали или отжали
  • `mousemove` -- при движении мыши
События на элементах управления
  • `submit` -- посетитель отправил форму ``
  • `focus` -- посетитель фокусируется на элементе, например нажимает на ``
Клавиатурные события
  • `keydown` -- когда посетитель нажимает клавишу
  • `keyup` -- когда посетитель отпускает клавишу
События документа
  • `DOMContentLoaded` -- когда HTML загружен и обработан, DOM документа полностью построен и доступен.
События CSS
  • `transitionend` -- когда CSS-анимация завершена.

Также есть и много других событий.

Назначение обработчиков событий

Событию можно назначить обработчик, то есть функцию, которая сработает, как только событие произошло.

Именно благодаря событиям JavaScript-код может реагировать на действия посетителя.

Есть несколько способов назначить событию обработчик. Сейчас мы их рассмотрим, начиная от самого простого.

Использование атрибута HTML

Обработчик может быть назначен прямо в разметке, в атрибуте, который называется on<событие>.

Например, чтобы прикрепить click-событие к input кнопке, можно присвоить обработчик onclick, вот так:

<input value="Нажми меня" *!*onclick="alert('Клик!')"*/!* type="button">

При клике мышкой на кнопке выполнится код, указанный в атрибуте onclick.

В действии:

Обратите внимание, для строки внутри alert('Клик!') используются одиночные кавычки, так как сам атрибут находится в двойных.

Частая ошибка новичков в том, что они забывают, что код находится внутри атрибута. Запись вида onclick="alert("Клик!")" не будет работать. Если вам действительно нужно использовать именно двойные кавычки, то это можно сделать, заменив их на &quot;: onclick="alert(&quot;Клик!&quot;)".

Однако, обычно этого не требуется, так как в разметке пишутся только очень простые обработчики. Если нужно сделать что-то сложное, то имеет смысл описать это в функции, и в обработчике вызвать уже её.

Следующий пример по клику запускает функцию countRabbits().

<!--+ src="2.html" run height=80 -->

Как мы помним, атрибут HTML-тега не чувствителен к регистру, поэтому ONCLICK будет работать так же, как onClick или onclick... Но, как правило, атрибуты пишут в нижнем регистре: onclick.

Использование свойства DOM-объекта

Можно назначать обработчик, используя свойство DOM-элемента on<событие>.

Пример установки обработчика click:

<input id="elem" type="button" value="Нажми меня"/>
<script>
*!*
elem.onclick = function() {
    alert('Спасибо');
};
*/!*
</script>

В действии:

Если обработчик задан через атрибут, то браузер читает HTML-разметку, создаёт новую функцию из содержимого атрибута и записывает в свойство onclick.

Обработчик хранится именно в свойстве, а атрибут -- лишь один из способов его инициализации.

Эти два примера кода работают одинаково:

  1. Только HTML:
    <!--+ run height=50 -->
    <input type="button" *!*onclick="alert('Клик!')"*/!* value="Кнопка"/>
    
  2. HTML + JS:
    <!--+ run height=50 -->
    <input type="button" id="button" value="Кнопка"/>
    <script>
    *!*
    button.onclick = function() {
      alert('Клик!');
    };
    */!*
    </script>
    

Так как свойство, в итоге, одно, то назначить более одного обработчика так нельзя.

В примере ниже назначение через JavaScript перезапишет обработчик из атрибута:

<!--+ run height=50 autorun -->
<input type="button" id="elem" onclick="alert('До')" value="Нажми меня"/>
<script>
*!*
elem.onclick = function() { // перезапишет существующий обработчик
  alert('После');
};
*/!*
</script>

Кстати, обработчиком можно назначить и уже существующую функцию:

function sayThanks() {
  alert('Спасибо!');
}

elem.onclick = sayThanks;

Если обработчик надоел -- его всегда можно убрать назначением elem.onclick = null.

Доступ к элементу через this

Внутри обработчика события this ссылается на текущий элемент, то есть на тот, на котором он сработал.

Это можно использовать, чтобы получить свойства или изменить элемент.

В коде ниже button выводит свое содержимое, используя this.innerHTML:

<button onclick="alert(this.innerHTML)">Нажми меня</button>

В действии:

Частые ошибки

Если вы только начинаете работать с событиями -- обратите внимание на следующие особенности.

Функция должна быть присвоена как `sayThanks`, а не `sayThanks()`.
button.onclick = sayThanks;

Если добавить скобки, то sayThanks() -- будет уже результат выполнения функции (а так как в ней нет return, то в onclick попадёт undefined). Нам же нужна именно функция.

...А вот в разметке как раз скобки нужны:

<input type="button" id="button" onclick="sayThanks()"/>

Это различие просто объяснить. При создании обработчика браузером по разметке, он автоматически создает функцию из его содержимого. Поэтому последний пример -- фактически то же самое, что:

button.onclick = function() {
*!*
  sayThanks();  // содержимое атрибута
*/!*
};
Используйте именно функции, а не строки.
Назначение обработчика строкой `elem.onclick = 'alert(1)'` будет работать, но не рекомендуется, могут быть проблемы при сжатии JavaScript.

Передавать код в виде строки по меньшей мере странно в языке, который поддерживает Function Expressions, оно здесь доступно только по соображениям совместимости с древними временами.

Не используйте `setAttribute`.
Такой вызов работать не будет:
//+ run
// при нажатии на body будут ошибки 
// потому что при назначении в атрибут функция будет преобразована в строку
document.body.setAttribute('onclick', function() { alert(1) });
Регистр свойства имеет значение.
Свойство называется `onclick`, а не `ONCLICK`.

Специальные методы

Фундаментальный недостаток описанных выше способов назначения обработчика -- невозможность повесить несколько обработчиков на одно событие.

Например, одна часть кода хочет при клике на кнопку делать ее подсвеченной, а другая -- выдавать сообщение. Нужно в разных местах два обработчика повесить.

При этом новый обработчик будет затирать предыдущий. Например, следующий код на самом деле назначает один обработчик -- последний:

input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // заменит предыдущий обработчик

Разработчики стандартов достаточно давно это поняли и предложили альтернативный способ назначения обработчиков при помощи специальных методов, который свободен от указанного недостатка.

addEventListener и removeEventListener

Методы addEventListener и removeEventListener являются современным способом назначить или удалить обработчик, и при этом позволяют использовать сколько угодно любых обработчиков.

Назначение обработчика осуществляется вызовом addEventListener с тремя аргументами:

element.addEventListener( event, handler, phase );
`event`
Имя события, например `click`
`handler`
Ссылка на функцию, которую надо поставить обработчиком.
`phase`
Фаза, на которой обработчик должен сработать. Этот аргумент мы рассмотрим далее в учебнике. Пока что будем использовать значение `phase = false`, которое нужно в 99% случаев.

Удаление обработчика осуществляется вызовом removeEventListener:

element.removeEventListener( event, handler, phase );

[warn header="Удаление требует ту же функцию"] Для удаления нужно передать именно ту функцию-обработчик которая была назначена.

Вот так removeEventListener не сработает:

input.addEventListener( "click" , function() {alert('Спасибо!')}, false);
// .... 
input.removeEventListener( "click", function() {alert('Спасибо!')}, false);

Это не одна и та же функция, а две независимо созданные (с одинаковым кодом, но это не важно).

Вот так правильно:

function handler() {
  alert('Спасибо!');
}

input.addEventListener( "click" , handler, false);
// .... 
input.removeEventListener( "click", handler, false);

[/warn]

Использование addEventListener позволяет добавлять несколько обработчиков на одно событие одного элемента:

<!--+ run -->
<input id="elem" type="button" value="Нажми меня"/>

<script>
  function handler1() {
    alert('Спасибо!');
  };
 
  function handler2() {
    alert('Спасибо ещё раз!');
  }

*!*
  elem.onclick = function() { alert("Привет"); };
  elem.addEventListener("click", handler1, false); // Спасибо! 
  elem.addEventListener("click", handler2, false); // Спасибо ещё раз!
*/!*
</script>

Как видно из примера выше, можно одновременно назначать обработчики и через onсвойство (только один) и через addEventListener. Однако, во избежание путаницы обычно рекомендуется выбрать один способ.

[warn header="addEventListener работает всегда, а onсвойство -- нет"] У специальных методов есть ещё одно перимущество перед "старой школой".

Есть некоторые события, которые нельзя назначить через onсвойство, но можно через addEventListener.

Например, таково событие transitionend, то есть окончание CSS-анимации. В большинстве браузеров оно требует назначения через addEventListener.

При нажатии на кнопку в примере ниже сработает второй обработчик, но не первый.

<!--+ autorun run -->
<style>
  button {
    transition: width 3s;
    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"); // сработает по окончании анимации
  }, false);
*/!*
</script>

[/warn]

Отличия IE8-

При работе с событиями в IE8- есть много отличий. Как правило, они формальны -- некое свойство или метод называются по-другому. Начиная с версии 9, также работают и стандартные свойства и методы, которые предпочтительны.

В IE8- вместо addEventListener/removeEventListener используются свои методы.

Назначение обработчика осуществляется вызовом attachEvent:

element.attachEvent( "on"+event, handler);

Удаление обработчика -- вызовом detachEvent:

element.detachEvent( "on"+event, handler);

Например:

function handler() {
    alert('Спасибо!');
}
button.attachEvent( "onclick" , handler) // Назначение обработчика
// .... 
button.detachEvent( "onclick", handler) // Удаление обработчика

Как видите, почти то же самое, только событие должно включать префикс on и нет третьего аргумента, который нам пока не нужен.

[warn header="У обработчиков, назначенных с attachEvent, нет this"] Обработчики, назначенные с attachEvent не получают this!

Это важная особенность и подводный камень старых IE. [/warn]

Кроссбраузерный способ назначения обработчиков

Можно объединить способы для IE<9 и современных браузеров, создав свои методы addEvent(elem, type, handler) и removeEvent(elem, type, handler):

var addEvent, removeEvent;

if (document.addEventListener) { // проверка существования метода
  addEvent = function(elem, type, handler) {
    elem.addEventListener(type, handler, false);
  };
  removeEvent = function(elem, type, handler) {
    elem.removeEventListener(type, handler, false);
  };
} else {
  addEvent = function(elem, type, handler) {
    elem.attachEvent("on" + type, handler);
  };
  removeEvent = function(elem, type, handler) {
    elem.detachEvent("on" + type, handler);
  };
}

...
// использование:
addEvent(elem, "click", function() { alert("Привет"); });

Это хорошо работает в большинстве случаев, но у обработчика не будет this в IE, потому что attachEvent не поддерживает this.

Кроме того, в IE7- есть проблемы с утечками памяти... Но если вам не нужно this, и вы не боитесь утечек (как вариант -- не поддерживаете IE7-), то это решение может подойти.

Итого

Есть три способа назначения обработчиков событий:

  1. Атрибут HTML: `onclick="..."`.
  2. Свойство: elem.onclick = function.
  3. Специальные методы:
    • Для IE8-: `elem.attachEvent( on+событие, handler )` (удаление через `detachEvent`).
    • Для остальных: `elem.addEventListener( событие, handler, false )` (удаление через `removeEventListener`).

Сравнение addEventListener и onclick: [compare] +Некоторые события можно назначить только через addEventListener. +Метод addEventListener позволяет назначить много обработчиков на одно событие. -Обработчик, назначенный через onclick, проще удалить или заменить. -Метод onclick кросс-браузерный. [/compare]

Этим введением мы только начинаем работу с событиями, но вы уже можете решать разнообразные задачи с их использованием.

[head]

[/head]