Введение в DOM события.

Щелчок мышкой, сенсорное нажатие, перетаскивание, изменение, ввод, сигнал ошибки, изменение размера — список возможных DOM (стандарт консорциума WWW, определяющий способы манипулирования объектами и изображениями на одной веб-странице) событий можно продолжать ещё очень долго.

События могут быть вызваны в любой части документа, будь то в результате операций пользователя или браузером. Они не просто начинаются и заканчиваются в одном месте; они представляют собой поток и формируют свой собственный жизненный цикл. Именно этот жизненный цикл делает DOM события такими гибкими и полезными. Мы, как разработчики, должны понимать особенности работы DOM событий. Только в этом случае мы сполна можем использовать свой потенциал и создавать оптимальный опыт пользовательского взаимодействия.

На протяжении своего пребывания на посту интерфейсного разработчика я никогда не давал прямых объяснений того, как работают DOM события. В этой статье я хотел бы дать вам информацию, которая позволила бы получить четкое представление о DOM событиях. Благодаря описанным здесь примерам вы сможете ощутимо ускорить выполнение поставленных задач.

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

Обработчики событий.

В прошлом браузеры имели серьезные несоответствия в способе передачи событий слушателей (процесс, обеспечивающий, например, контроль правильности выражений, вводимых с клавиатуры) в DOM узлы. Такие библиотеки, как jQuery внесли неоценимый вклад в улучшение существующей ситуации.

По мере продвижения к более стандартизированной браузерной среде, мы можем без опасений использовать API-интерфейсы из официальной спецификации. Чтобы не усложнять общую картину я просто опишу, как управлять событиями в современном интернете. Если вы создаете JavaScript код для Internet Explorer (IE) 8 или версий ниже, я бы посоветовал использовать polyfill или специализированный фреймворк (например, jQuery) для управления событием слушателей.

В JavaScript мы можем обрабатывать события с помощью следующей функции:

element.addEventListener(<event-name>, <callback>, <use-capture>);
  • event-name (строковое значение) – это имя или тип события, которое вы хотели бы обработать. Это может быть любое из стандартных DOM событий. (click, mousedown, touchstart, transitionEnd и т.д.) или даже собственные названия события (мы поговорим о пользовательских событиях позже).
  • callback (функции) – эта функция вызывается, когда происходит событие. Объект event, содержащий данные о событии, передается в качестве первого аргумента.
  • use-capture (логическое значение) – это объявление отвечает за установление обратной связи на стадии «сбора данных» (не волнуйтесь: мы объясним, что означает этот термин немного позднее).
var element = document.getElementById('element');

function callback() {
  alert('Hello');
}

// Add listener
element.addEventListener('click', callback);

Демо: addEventListener.

Удаление обработчиков.

Обработчики событий необходимо удалять, как только они теряют свою актуальность. Это наиболее правильное решение (особенно для веб-приложений с долгосрочной перспективой). Чтобы сделать это, используйте метод element.removeEventListener():

element.removeEventListener(<event-name>, <callback>, <use-capture>);

Но у метода removeEventListener есть одна особенность: вы должны размещать ссылку на функцию обратной связи. Простой вызов element.removeEventListener('click'); не будет работать.

Проще говоря, если мы не заинтересованы в удалении обработчиков событий (при работе над «долгосрочными» приложениями), то должны иметь возможность контролировать обратные связи. Это означает, что мы не можем использовать безымянные функции.

var element = document.getElementById('element');

function callback() {
  alert('Hello once');
  element.removeEventListener('click', callback);
}

// Add listener
element.addEventListener('click', callback);

Демо: removeEventListener.

Следите за правильностью контекста обратной связи.

Вызов обратной связи при неверном контексте может привести к возникновению ошибки. Поясним на примере.

var element = document.getElementById('element');

var user = {
 firstname: 'Wilson',
 greeting: function(){
   alert('My name is ' + this.firstname);
 }
};

// Attach user.greeting as a callback
element.addEventListener('click', user.greeting);

// alert => 'My name is undefined'

Демо: неправильный контекст обратной связи.

Использование безымянных функций.

Мы ожидали обратной связи с правильной смысловой фразой My name is Wilson. На самом же деле нам была выведена фраза My name is undefined. Для this.firstName должно возвращаться значение Wilson, user.greeting, которое вызывается в зависимости от контекста user.

Когда мы передаем greeting функцию в addEventListener метод, то мы всего лишь передаем ссылку на функцию; контекст user не передаются. Внутренне, обратная связь вызывается в контексте element, который означает, что this относится к element, а не к user. Поэтому this.firstname является неопределенным.

Существует два способа предотвращения контекстного несоответствия. Во-первых, мы можем вызвать user.greeting() с правильным контекстом внутри безымянной функции.

element.addEventListener('click', function() {
  user.greeting();
  // alert => 'My name is Wilson'
});

Демо: безымянные функции.

Function.prototype.bind

Последний метод не так хорош, поскольку теперь у нас нет идентификатора функции, который позволил бы удалить её с помощью .removeEventListener(). Кроме того, общий вид программы был бы довольно уродливым. Я предпочитаю использовать метод .bind() (встроен во все функции, начиная с ECMAScript 5) для создания новой функции (bound), которая всегда будет работать в данном контексте. Затем мы передаем эту функцию как обратную связь в .addEventListener().

// Overwrite the original function with
// one bound to the context of 'user'
user.greeting = user.greeting.bind(user);

// Attach the bound user.greeting as a callback
button.addEventListener('click', user.greeting);

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

button.removeEventListener('click', user.greeting);

Демо: Function.prototype.bind.

Событие как объект.

Событие как объект создается при первом своем появлении; этот объект продолжает существовать в рамках DOM. Функция, которую мы выбрали в качестве обратной связи для обработчика события, передается объекту события в качестве первого аргумента. Мы можем использовать этот объект для доступа к огромному количеству информации о событии, которое произошло:

  • type (строковое значение). Это название события.
  • target (узел). Это DOM узел, в котором создается событие.
  • currentTarget (узел). Это DOM узел, в котором происходит обратный вызов события.
  • bubbles (булевое значение). Оно указывает, является ли это событие “пузырьковым” (восходящим) (значение этого термина мы объясним позже).
  • preventDefault (функция). Эта функция предотвращает установление поведения по умолчанию. Смена поведения по отношению к событию может быть инициирована пользовательским агентом, например, браузером (например, ограничение click события по <a> элементу в момент загрузки новой страницы).
  • stopPropagation (функция). Эта функция ограничивает любые обратные связи на всех узлах вдоль цепи событий. Однако это не мешает дополнительным обратным связям с одноименным названием события запускаться на текущем узле (мы поговорим об этом немного позднее).
  • stopImmediatePropagation (функция). Эта функция ограничивает любые обратные связи на всех узлах вдоль цепи событий, включая любые дополнительные обратные связи с одноименным названием события в пределах текущего узла.
  • cancelable (булевое значение). Оно определяет, может ли поведение по умолчанию для события быть ограничено путем вызова event.preventDefault метода.
  • defaultPrevented (булевое значение). Это значение указывает, что preventDefault метод был вызван для объекта события.
  • isTrusted (булевое значение). Событие будет «доверительным», если оно исходит от самого устройства, а не создается внутри JavaScript.
  • eventPhase (количественное значение). Это число указывает на фазу, в которой в настоящее время находится событие: отсутствие (0), сбор данных (1), целевая фаза (2) или «выделение пузырьков» (3). Далее мы более подробно рассмотрим все фазы события.
  • timestamp (количественное значение). Время выполнения события.

Существуют также и другие свойства, но они носят специфический характер. Например, события мыши будут включать в себя clientX и clientY свойства, которые позволяют определить текущее местоположение указателя в окне просмотра.

Лучше всего использовать отладчик в вашем любимом браузере или console.log, который позволяет более внимательно сосредоточиться на объекте события и его свойствах.

Фазы события.

Запуск DOM события в вашем приложении это не просто единичный процесс; он состоит из трех этапов. Короче говоря, событие представляет собой поток, который берет начало в корне документа и направлен к целевому объекту (т.е. фаза сбора данных), затем происходит запуск целевого события (целевая фаза), а затем поток направляется обратно в корень документа (фаза «создания пузырьков»).

eventflow 

(Источник изображения: W3C).

Демо: подробная структура события.

Фаза сбора данных.

Первый этап состоит из фазы сбора данных. Событие начинает свой путь в корне документа, проходит вниз через каждый DOM слой, запускаясь на каждом узле, пока не достигнет цели. Суть фазы сбора данных заключается в создании маршрута, по которому событие будет переходить к фазе «создания пузырьков».

Как уже упоминалось ранее, вы можете обрабатывать события в фазе сбора данных, установив третий аргумент addEventListener в значение true. Я нашел не так уж много случаев использования обработчиков фазы сбора. Теоретически вы могли бы ограничить запуск определенных элементов по клику, если событие обрабатывается в фазе сбора.

var form = document.querySelector('form');

form.addEventListener('click', function(event) {
  event.stopPropagation();
}, true); // Note: 'true'

Если вы не уверены касательно необходимости ограничений, установите для событий в фазе «создания пузырьков» useCapture флажок в значение false или undefined.

Целевая фаза.

Процесс достижения событием цели называется целевой фазой. Перед изменением направления на обратное и повторением шагов, направленных назад к внешнему уровню документа событие запускается в целевом узле.

В случае использования вложенных элементов, события срабатывания мышки всегда нацелены на наиболее глубоко вложенный элемент. Если вы захотите обработать click событие на <div> элементе, а пользователь будет нажимать на <p> элемент, то <p> элемент станет целью события. События «образования пузырьков» означают, что вы можете обрабатывать клики по <div> (или любому другому предшествующему узлу) и все еще поддерживать обратную связь.

Фаза создания пузырьков.

После того, как событие достигает своей цели, его рабочий цикл на этом не заканчивается. Оно переходит вверх (или распространяется) по DOM дереву, пока не достигнет корня документа. Это означает, что событие запускается в целевом родительском узле и продолжается до тех пор, пока один из родителей не сделает проход (в программировании выполнение некоторой последовательности операций) к событию.

Представьте себе, что DOM – это многослойная сфероидальная структура, а цель события – это основа этой структуры. В целевой фазе событие проходит через каждый слой структуры. Когда оно достигает ядра, оно запускается (целевая фаза), а затем начинает свой путь в обратном направлении вверх через каждый слой (фаза продвижения). Как только событие возвращается на поверхность, его движение окончено.

Фаза «создания пузырьков» очень полезна. Этот процесс освобождает нас от необходимости обрабатывать события на элементе, из которого они происходят. Вместо этого, мы двигаемся далее вверх по DOM дереву, ожидая отработки события. Если бы фаза «создания пузырьков» отсутствовала, то мы должны были бы, в некоторых случаях, обрабатывать события на различных элементах. Это необходимо для того, чтобы зафиксировать событие.

Демо: определение фаз события.

Фаза «создания пузырьков» характерна для большинства, но не для всех событий. Если события не «пузырятся», то это, как правило, происходит по уважительной причине. Если вы сомневаетесь, проверьте спецификацию.

Остановка продвижения.

Прервать поток события в любой точке маршрута (т.е. в фазах сбора данных или «создания пузырьков») можно простым вызовом stopPropagation метода в объекте события. В этом случае события больше не будет вызывать обработчики в узлах, через которые они проходят на пути к цели и обратно к документу.

child.addEventListener('click', function(event) {
 event.stopPropagation();
});

parent.addEventListener('click', function(event) {
 // If the child element is clicked
 // this callback will not fire
});

Вызов event.stopPropagation() не ограничит каких-либо дополнительн …

Если вы хотите прочитать полностью статью, посетите сайт наших спонсоров

Comments are closed.