Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

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

  1. Назначение обработчиков событий
    1. Использование атрибута HTML
    2. Использование свойства DOM-объекта
    3. Частые ошибки
    4. Доступ к элементу, this
    5. Недостатки назначения через onсобытие
  2. Специальные методы
    1. Методы IE<9
    2. Назначение обработчиков по стандарту
      1. Особенности специальных методов
    3. Кроссбраузерный способ назначения обработчиков
  3. Итого

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

Событие - это сигнал от браузера о том, что что-то произошло.

Существует много видов событий.

  • 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("Клик")" не будет работать. Если вам действительно нужно использовать именно двойные кавычки, то это можно сделать, заменив их на &quot;: onclick="alert(&quot;Клик&quot;)".

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

Следующий пример по клику запускает функцию 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.

Первичным является именно свойство, а атрибут — лишь способ его инициализации.

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

  1. Только HTML:
    <input type="button" *!*onclick="alert('Клик!')"*/!* value="Кнопка"/>
    
  2. 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) });
    

    Как вы думаете, почему?

    Раскрыть ответ

    Значением атрибута может быть только строка. Любое другое значение преобразуется в строку.

    Функция в строчном виде обычно даёт свой код: "function() { alert(1) }".

    Итак, атрибут присвоен. Теперь браузер создаст обработчик с телом из этой строки:

    document.body.onclick = function() {  
      function() { alert(1) } 
    }
    
    Этот код попросту некорректен с точки зрения JavaScript. Локальная функция объявлена без имени — отсюда и ошибка.

  • Используйте функции, а не строки.

    Запись 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) // удаление обработчика
    

    Особенности специальных методов

    1. Можно поставить столько обработчиков, сколько вам нужно.
    2. Нельзя получить назначенные обработчики из элемента.
    3. Браузер не гарантирует сохранение порядка выполнения обработчиков. Они могут быть назначены в одном порядке, а выполниться — в другом.
    4. Кроссбраузерные несовместимости.

    Относительно пункта (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), то это решение может подойти.

    Итого

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

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

    Все способы, кроме attachEvent, обеспечивают доступ к элементу, на котором сработал обработчик, через this.

    Последний аргумент addEventListener мы рассмотрим позже, в статье Всплытие и перехват. Он почти всегда равен false.

    Создайте меню, которое раскрывается/сворачивается при клике:

    Исходный документ и картинки: tutorial/browser/events/sliding-src/index.html.

    HTML/CSS, возможно, понадобится немного изменить.

    Решение, шаг 1
    Решение
    Решение, шаг 1

    Для начала, зададим структуру 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.

    Решение, шаг 2
    Решение, шаг 2

    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.

    Решение, шаг 3
    Решение, шаг 3

    Решение: tutorial/browser/events/sliding/index.html.

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

    Результат:

    Как лучше отобразить кнопку справа-сверху: position:absolute или float?

    Исходный документ tutorial/browser/events/messages-src/index.html.

    Алгоритм решения
    Решение
    Алгоритм решения
    1. Разработать структуру HTML/CSS. Позиционировать кнопку внутри сообщения.
    2. Найти все кнопки
    3. Присвоить им обработчики
    4. Обработчик будет сообщение по кнопке и удалять его
    Вёрстка
    Вёрстка

    Исправьте 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

    HTML/CSS
    Решение
    HTML/CSS
    Лента изображений должна быть оформлена как список, согласно принципам семантической вёрстки.

    Нужно стилизовать его так, чтобы он был длинной лентой, из которой внешний DIV вырезает нужную часть для просмотра:

    Чтобы список был длинный и элементы не переходили вниз, ему ставится width: 9999px, а элементам, соответственно, float:left.

    Не используйте display:inline

    Элементы с display:inline имеют дополнительные отступы для возможных «хвостов букв».

    В частности, для img нужно поставить в стилях явно display:block, чтобы пространства под ними не оставалось.

    При прокрутке UL сдвигается назначением margin-left:

    У внешнего DIV фиксированная ширина, поэтому «лишние» изображения обрезаются.

    Снаружи окошка находятся стрелки и внешний контейнер.

    Реализуйте эту структуру, и к ней прикручивайте обработчики, которые меняют ul.style.marginLeft.

    Полное решение
    Полное решение

    Решение: tutorial/browser/events/carousel/index.html


Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!