Для реакции на действия посетителя и внутреннего взаимодействия скриптов существуют события.
Событие - это сигнал от браузера о том, что что-то произошло.
Существует много видов событий.
- DOM-события, которые инициализируются элементами DOM. Например:
- Событие
clickпроисходит, когда кликнули на элемент - Событие
mouseover— когда на элемент наводится мышь. - Событие
focus— когда посетитель фокусируется на элементе.
- Событие
- События для окна браузера. Например,
resize— когда изменяется размер окна. - Есть загрузки файла/документа:
load,readystatechange,DOMContentLoaded…
События соединяют JavaScript-код с документом и посетителем, позволяя создавать динамические интерфейсы.
Назначение обработчиков событий
Для того, чтобы скрипт среагировал на событие, в нем должна быть описана специальная функция.
Функции, которые реагируют на события, называются обработчиками событий (event handlers). Обычно их название имеет вид "on+тип события", например: onclick.
Есть несколько способов назначить событию обработчик. Сейчас мы их рассмотрим, начиная от самого простого.
Использование атрибута HTML
Обработчик может быть назначен прямо в разметке, в атрибуте, который называется on<событие>.
Например, чтобы прикрепить click-событие к input кнопке, можно присвоить обработчик onclick, вот так:
<input id="b1" value="Нажми меня" *!*onclick="alert('Спасибо!')"*/!* type="button"/>
В действии:
В этом внутри alert используются одиночные кавычки, так как сам атрибут находится в двойных.
Частая ошибка новичков в том, что они забывают, что код находится внутри атрибута. Запись вида onclick="alert("Клик")" не будет работать. Если вам действительно нужно использовать именно двойные кавычки, то это можно сделать, заменив их на ": onclick="alert("Клик")".
Однако, обычно этого не требуется, так как в разметке пишутся только очень простые обработчики. Если нужно сделать что-то сложное, то имеет смысл описать это в функции, и в обработчике вызвать её.
Следующий пример по клику запускает функцию countRabbits().
<!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.
Такой способ назначения обработчиков очень удобен тем, что для очень простых вещей не нужно писать JavaScript отдельно, получается «всё-в-одном».
Использование свойства DOM-объекта
Можно назначать обработчик, используя свойство DOM-элемента on<событие>.
Пример установки обработчика click элементу с id="myElement":
<input id="myElement" type="button" value="Нажми меня"/>
<script>
var elem = document.getElementById('myElement');
*!*
elem.onclick = function() {
alert('Спасибо');
}
*/!*
</script>
В действии:
Если обработчик задан через атрибут, то соответствующее свойство появится у элемента автоматически. Браузер читает HTML-разметку, создаёт новую функцию из содержимого атрибута и записывает в свойство onclick.
Первичным является именно свойство, а атрибут — лишь способ его инициализации.
Эти два примера кода работают одинаково:
- Только HTML:
<input type="button" *!*onclick="alert('Клик!')"*/!* value="Кнопка"/> - HTML + JS:
<input type="button" id="button" value="Кнопка"/> <script> *!* document.getElementById('button').onclick = function() { alert('Клик!'); } */!* </script>
Так как свойство, в итоге, одно, то назначить по обработчику и там и там нельзя.
В примере ниже, назначение через JavaScript перезапишет обработчик из атрибута:
<input type="button" onclick="alert('До')" value="Нажми меня"/>
<script>
var elem = document.getElementsByTagName('input')[0];
*!*
elem.onclick = function() { // перезапишет существующий обработчик
alert('После');
}
*/!*
</script>
Обработчиком можно назначить уже существующую функцию:
function sayThanks() {
alert('Спасибо!');
}
document.getElementById('button').onclick = sayThanks;
Частые ошибки
- Функция должна быть присвоена как
sayThanks, а неsayThanks():document.getElementById('button').onclick = sayThanks;Если добавить скобки, то
sayThanks()— будет уже результат выполнения функции (а так как в ней нетreturn, то вonclickпопадётundefined). Нам же нужна именно функция...А вот в разметке как раз скобки нужны:
<input type="button" id="button" onclick="sayThanks()"/>
Это различие просто объяснить. При создании обработчика браузером по разметке, он автоматически создает функцию из его содержимого. Поэтому, последний пример — фактически тоже самое, что:
document.getElementById('button').onclick = function() { *!* sayThanks(); // содержимое атрибута */!* } - Используйте свойство, а не атрибут. Так неверно:
elem.setAttribute('onclick', func).Хотя, с другой стороны, если
func— строка, то такое присвоение будет успешным, например:
// сработает, будет при клике выдавать 1 document.body.setAttribute('onclick', 'alert(1)');Браузер в этом случае сделает функцию-обработчик с телом из строки
alert(1).…А вот если
func— не строка, а функция (как и должно быть), то работать совсем не будет:
// при нажатии на body будут ошибки document.body.setAttribute('onclick', function() { alert(1) });Как вы думаете, почему?
- Используйте функции, а не строки.
Запись
elem.onclick = 'alert(1)'будет работать, но не рекомендуется.При использовании в такой функции-строке переменных из замыкания будут проблемы с JavaScript-минификаторами. Здесь мы не будем вдаваться в детали этих проблем, но общий принцип такой — функция должна быть
function. - Названия свойств регистрозависимы, поэтому
on<событие>должно быть написано в нижнем регистре. СвойствоONCLICKработать не будет.
Доступ к элементу, this
Внутри обработчика события, this ссылается на текущий элемент. Это можно использовать, чтобы получить свойства или изменить элемент.
Ниже, button выводит свое содержимое, используя this.innerHTML:
<button onclick="alert(this.innerHTML)">Нажми меня</button>В действии:
Используя JavaScript, сделайте так, чтобы при клике на кнопку исчезал элемент с id="hide". Демо:
Исходный документ: tutorial/browser/events/hide/hideOtherSource.html.
Решение задачи: tutorial/browser/events/hide/hideOther.html.
Создайте кнопку, при клике на которую, она будет скрывать сама себя.
Как эта:
Решение задачи заключается в использовании this в обработчике.
<input type="button" onclick="this.style.display='none'" value="Нажми, чтобы спрятать"/>
Недостатки назначения через onсобытие
Фундаментальный недостаток описанных способов назначения обработчика — невозможность повесить несколько обработчиков на одно событие.
Например, одна часть кода хочет при клике на кнопку делать ее подсвеченной, а другая — выдавать сообщение. Нужно в разных местах два обработчика повесить.
При этом новый обработчик будет затирать предыдущий. Например, следующий код на самом деле назначает один обработчик — последний:
input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // заменит предыдущий обработчик
Конечно, это можно обойти разными способами, в том числе написанием фреймворка вокруг обработчиков. Но существует и другой метод назначения обработчиков, который свободен от указанного недостатка.
Специальные методы
Для назначения обработчиков существуют специальные методы. Как правило, в браузерах они стандартные, кроме IE<9, где они похожи, но немного другие.
Методы IE<9
Сначала посмотрим метод для старых IE, т.к. оно чуть проще.
Назначение обработчика осуществляется вызовом attachEvent:
element.attachEvent( "on"+event, handler);
Удаление обработчика — вызовом detachEvent:
element.detachEvent( "on"+event, handler);
Например:
var input = document.getElementById('button')
function handler() {
alert('спасибо!')
}
input.attachEvent( "onclick" , handler) // Назначение обработчика
// ....
input.detachEvent( "onclick", handler) // Удаление обработчика
Обычно, обработчики ставятся. Но бывают ситуации, когда их нужно удалять или менять.
В этом случае нужно передать в метод удаления именно функцию-обработчик. Такой вызов будет неправильным:
input.attachEvent( "onclick" ,
function() {alert('Спасибо!')}
)
// ....
input.detachEvent( "onclick",
function() {alert('Спасибо!')}
)
Несмотря на то, что функции работают одинаково, это две разных функции.
Использование attachEvent позволяет добавлять несколько обработчиков на одно событие одного элемента.
Пример ниже будет работать только в IE и Opera:
<input id="myElement" type="button" value="Нажми меня"/>
<script>
var myElement = document.getElementById("myElement")
var handler = function() {
alert('Спасибо!')
}
var handler2 = function() {
alert('Спасибо еще раз!')
}
*!*
myElement.attachEvent("onclick", handler); // первый
myElement.attachEvent("onclick", handler2); // второй
*/!*
</script>
attachEvent, нет thisОбработчики, назначенные с attachEvent не получают this!
Это важная особенность и подводный камень старых IE.
Назначение обработчиков по стандарту
Официальный способ назначения обработчиков из стандарта W3C работает во всех современных браузерах, включая IE9+.
Назначение обработчика:
element.addEventListener( event, handler, phase);
Удаление:
element.removeEventListener( event, handler, phase);
Как видите, похоже на attachEvent/detachEvent, только название события пишется без префикса «on».
Еще одно отличие от синтаксиса Microsoft - это третий параметр - phase, которые обычно не используется и выставлен в false. Позже мы посмотрим, что он означает.
Использование этого метода — такое же, как и у attachEvent:
function handler() { ... }
elem.addEventListener( "click" , handler, false) // назначение обработчика
elem.removeEventListener( "click", handler, false) // удаление обработчика
Особенности специальных методов
- Можно поставить столько обработчиков, сколько вам нужно.
- Нельзя получить назначенные обработчики из элемента.
- Браузер не гарантирует сохранение порядка выполнения обработчиков. Они могут быть назначены в одном порядке, а выполниться — в другом.
- Кроссбраузерные несовместимости.
Относительно пункта (2) — браузерные средства отладки позволяют получить назначенные обработчики, но из JavaScript этого сделать нельзя.
Кроссбраузерный способ назначения обработчиков
Можно объединить способы для 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('hi') })
Это хорошо работает в большинстве случаев, но у обработчика не будет this в IE, потому что attachEvent не поддерживает this.
Кроме того, в IE<8 есть проблемы с утечками памяти… Но если вам не нужно this, и вы не боитесь утечек (как вариант — не поддерживаете IE<8), то это решение может подойти.
Итого
Есть три способа назначения обработчиков событий:
- Атрибут HTML:
onclick="...". - Свойство:
elem.onclick = function. - Специальные методы:
- Для IE<9:
elem.attachEvent( on+событие, handler )(удаление черезdetachEvent). - Для остальных:
elem.addEventListener( событие, handler, false )(удаление черезremoveEventListener).
- Для IE<9:
Все способы, кроме attachEvent, обеспечивают доступ к элементу, на котором сработал обработчик, через this.
Последний аргумент addEventListener мы рассмотрим позже, в статье Всплытие и перехват. Он почти всегда равен false.
Создайте меню, которое раскрывается/сворачивается при клике:
Исходный документ и картинки: tutorial/browser/events/sliding-src/index.html.
HTML/CSS, возможно, понадобится немного изменить.
Для начала, зададим структуру HTML/CSS.
Меню является отдельным графическим компонентом, его лучше поместить в единый DOM-элемент.
Элементы меню с точки зрения семантики являются списком UL/LI. Заголовок должен быть отдельным кликабельным элементом.
Получаем структуру:
<div class="menu">
<span class="title">Сладости (нажми меня)!</span>
<ul>
<li>Пирог</li>
<li>Пончик</li>
<li>Мед</li>
</ul>
</div>
Для заголовка лучше использовать именно SPAN, а не DIV, так как DIV постарается занять 100% ширины, и мы не сможем ловить click только на тексте:
<div style="border: solid red 1px">[Сладости (нажми меня)!]</div>
…А SPAN — это элемент с display: inline, поэтому он занимает ровно столько места, сколько занимает текст внутри него:
<span style="border: solid red 1px">[Сладости (нажми меня)!]</span>
Раскрытие/закрытие делайте путём добавления/удаления класса .menu-open к меню, которые отвечает за стрелочку и отображение UL.
CSS для меню:
.menu ul {
margin: 0;
list-style: none;
padding-left: 20px;
display: none;
}
.menu .title {
padding-left: 16px;
font-size: 18px;
cursor: pointer;
background: url(arrow-right.png) left center no-repeat;
}
Раскрытое меню перезаписывает соответствующие свойства:
.menu-open .title {
background: url(arrow-down.png) left center no-repeat;
}
.menu-open ul {
display: block;
}
Теперь сделайте JavaScript.
Есть список сообщений. Добавьте каждому сообщению по кнопке для его скрытия.
Результат:
Как лучше отобразить кнопку справа-сверху: position:absolute или float?
Исходный документ tutorial/browser/events/messages-src/index.html.
- Разработать структуру HTML/CSS. Позиционировать кнопку внутри сообщения.
- Найти все кнопки
- Присвоить им обработчики
- Обработчик будет сообщение по кнопке и удалять его
Исправьте HTML/CSS, чтобы кнопка была в нужном месте сообщения. Кнопку лучше сделать как div, а кнопка — его background. Это более правильно, чем img, т.к. в данном случае картинка является оформлением кнопки, а оформление должно быть в CSS.
Расположить кнопку справа можно при помощи position: relative для pane, а для кнопки position: absolute + right/top. Можно добавить padding-right к заголовку, чтобы избежать перекрытия кнопки и текста (для длинных заголовков).
Потенциальном преимуществом способа с position по сравнению с float в данном случае является возможность поместить элемент кнопки после текста, а не до него.
Для того, чтобы получить кнопку из контейнера, можно найти все IMG в нём и выбрать из них кнопку по className. На каждую кнопку можно повесить обработчик.
Решение показано тут: tutorial/browser/events/messages/index.html.
Напишите «Карусель» — ленту изображений, которую можно листать влево-вправо нажатием на стрелочки.
В дальнейшем к ней можно легко добавить анимацию, динамическую подгрузку и другие возможности.
В этой задаче разработка HTML/CSS-структуры составляет 90% решения.
Исходный документ: tutorial/browser/events/carousel-src/index.html
Нужно стилизовать его так, чтобы он был длинной лентой, из которой внешний DIV вырезает нужную часть для просмотра:

Чтобы список был длинный и элементы не переходили вниз, ему ставится width: 9999px, а элементам, соответственно, float:left.
Элементы с display:inline имеют дополнительные отступы для возможных «хвостов букв».
В частности, для img нужно поставить в стилях явно display:block, чтобы пространства под ними не оставалось.
При прокрутке UL сдвигается назначением margin-left:

У внешнего DIV фиксированная ширина, поэтому «лишние» изображения обрезаются.
Снаружи окошка находятся стрелки и внешний контейнер.
Реализуйте эту структуру, и к ней прикручивайте обработчики, которые меняют ul.style.marginLeft.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.