Делегирование событий в javascript
Содержание:
readyState
В заключение
занятия отметим свойство
document.readyState
которое в момент
загрузки HTML-документа принимает
следующие значения:
-
«loading»
– документ в процессе загрузки; -
«interactive»
– документ был полностью прочитан (парсинг документа завершен); -
«complete»
– документ был полностью прочитан и все ресурсы (изображения, стили и т.п.)
тоже загружены.
В ряде случаев
это свойство бывает весьма полезно. Например, мы вызываем функцию, но не
уверены, что DOM-дерево
полностью построено. Поэтому, делаем такую проверку:
removeImage(); function removeImage() { if(document.readyState == "loading") { console.log("документ грузится, вешаем обработчик"); document.addEventListener("DOMContentLoaded", removeImage); } else { console.log("удаляем изображение"); document.body.remove(image); } }
По аналогии
могут быть обработаны и остальные свойства.
Для полноты картины
пару слов о событии readystatechange, которое появилось до событий
DOMContentLoaded, load, unload, beforeunload
и в старых версиях
JavaScript процесс
загрузки документа контролировался через него. Например, так:
document.addEventListener('readystatechange', function() { console.log(document.readyState); });
Теперь при
обновлении страницы мы можем увидеть изменение состояний свойства document.readyState в процессе
загрузки. Однако такой механизм отслеживания ушел в прошлое и сейчас уже нет смысла
о нем подробно говорить.
Итак, на этом
занятии мы с вами рассмотрели события
DOMContentLoaded,
load, unload, beforeunload
и поговорили о свойстве
document.readyState
которое
дополняет работу с этими событиями.
Видео по теме
JavaScipt (DOM) #1: объектная модель документа DOM и BOM
JavaScipt (DOM) #2: навигация по DOM — parentNode, nextSibling, previousSibling, chidNodes
JavaScipt (DOM) #3: методы поиска элементов в DOM: querySelector, querySelectorAll, getElementById
JavaScipt (DOM) #4: свойства DOM-узлов: nodeName, innerHTML, outerHTML, data, textContent, hidden
JavaScipt (DOM) #5: работа с нестандартными свойствами DOM-элементов: getAttribute, setAttribute, dataset
JavaScipt (DOM) #6: создание и добавление элементов DOM createElement, append, remove, insertAdjacentHTML
JavaScipt (DOM) #7: управление стилями — className, style, classList, getComputedStyle
JavaScipt (DOM) #8: метрики — clientWidth, scrollTop, scrollHeight, offsetLeft, offsetTop, clientLeft
JavaScipt (DOM) #9: HTML-документ: размеры (clientWidth, innerWidth), положение (pageYOffset, scrollBy)
JavaScipt (DOM) #10: расположение элементов — fixed, absolute, getBoundingClientRect, elementFromPoint
JavaScipt (DOM) #11: обработчики событий: onclick, addEventListener, removeEventListener, event
JavaScipt (DOM) #12: погружение и всплытие событий: stopPropagation, stopImmediatePropagation, eventPhase
JavaScipt (DOM) #13: делегирование событий, отмена действия браузера по умолчанию — preventDefault
JavaScipt (DOM) #14: события мыши mousedown, mouseup, mousemove, mouseover, mouseout, mouseenter
JavaScipt (DOM) #15: события клавиатуры keydown, keyup, событие скроллинга scroll
JavaScipt (DOM) #16: навигация и обработка элементов форм (form) — document.forms, form.elements
JavaScipt (DOM) #17: фокусировка — focus, blur, focusin, focusout, tabindex, activeElement
JavaScipt (DOM) #18: события change, input, cut, copy, paste, submit элементов input и select
JavaScipt (DOM) #19: события при загрузке — DOMContentLoaded, load, unload, beforeunload, readyState
JavaScipt (DOM) #20: события load, error; атрибуты async, defer тега script
JavaScipt (DOM) #21: пример предзагрузки изображений с помощью javascript
JavaScipt (DOM) #22: пример создания начала игры арканоид
Объект «событие» (event)
Объект событие всегда передается обработчику и содержит массу полезной информации о том где и какое событие произошло.
Способов передачи этого объекта обработчику существует ровно два, и они зависят от способа его установки и от браузера.
В браузерах, работающих по рекомендациям W3C, объект события всегда передается в обработчик первым параметром.
Например:
function doSomething(event) { // event - будет содержать объект события } element.onclick = doSomething;
При вызове обработчика объект события будет передан ему первым аргументом.
Можно назначить и вот так:
element.onclick = function(event) { // event - объект события }
Интересный побочный эффект — в возможности использования переменной при назначении обработчика в HTML:
<input type="button" onclick="alert(event)" value="Жми сюда не ошибешься"/>
Это работает благодаря тому, что браузер автоматически создает функцию-обработчик с данным телом, в которой первый аргумент .
В Internet Explorer существует глобальный объект , который хранит в себе информацию о последнем событии. А первого аргумента обработчика просто нет.
То есть, все должно работать так:
// обработчик без аргументов function doSomething() { // window.event - объект события } element.onclick = doSomething;
Обратите внимание, что доступ к при назначении обработчика в HTML (см. пример выше) по-прежнему будет работать
Такой вот надежный и простой кросс-браузерный доступ к объекту события.
Можно кросс-браузерно получить объект события, использовав такой приём:
function doSomething(event) { event = event || window.event // Теперь event - объект события во всех браузерах. } element.onclick = doSomething
Как мы уже говорили раньше, при описании обработчика события в HTML-разметке для получения события можно использовать переменную с названием .
<input type="button" onclick="alert(event.type)" value="Нажми меня"/>
Этот код в действии:
Это совершенно кросс-браузерный способ, так как по стандарту — название первого аргумента функции-обработчика, которую автоматом создаст браузер; ну а в IE значение будет взято из глобального объекта .
Добавление обработчика через свойство DOM объекта
Второй способ назначить обработчик — это использовать свойство .
Например, привяжем обработчик события к элементу (для этого события свойство будет ):
<!-- HTML код кнопки --> <button type="button" id="my-btn">Нажми на меня</button> <!-- Скрипт на JavaScript --> <script> // получим кнопку и сохраним ссылку на неё в переменную const $btn = document.querySelector('#my-btn'); // добавим к $btn обработчик события click $btn.onclick = function() { alert('Вы кликнули на кнопку!'); } </script>
В приведённом выше примере обработчик представляет собой анонимную функцию, которая будет выполняться всякий раз, когда это событие на указанном элементе будет происходить.
Другой вариант – это назначить уже существующую функцию.
Например:
function changeBgColor() { document.body.style.backgroundColor = `rgb(${Math.round(Math.random()*255)}, ${Math.round(Math.random()*255)}, ${Math.round(Math.random()*255)})`; } document.onclick = changeBgColor;
Внутри обработчика можно обратиться к текущему элементу, т.е. к тому для которого в данный момент был вызван этот обработчик. Осуществляется это с помощью ключевого слова .
Например:
<!-- HTML код кнопок --> <button type="button">Кнопка 1</button> <button type="button">Кнопка 2</button> <button type="button">Кнопка 3</button> <!-- Скрипт на JavaScript --> <script> function message() { // this - обращаемся к кнопке для которой вызван обработчик alert(this.textContent); } // получим кнопки и сохраним ссылки на них в переменную $btns const $btns = document.querySelectorAll('button'); // переберём кнопки и добавим к ним обработчик, используя onclick $btns.forEach(function($element) { $element.onclick = message; }); </script>
Кстати, когда обработчик задаётся через атрибут, то браузер самостоятельно при чтении такого HTML создаёт из значения этого атрибута функцию и присваивает её одноименному свойству этого элемента.
Например:
<button id="btn" type="button" onclick="alert('Вы кликнули на кнопку')">Кнопка</button> <script> const $element = document.querySelector('#btn'); // получим значение свойства onclick (как видно браузер туда автоматически записал функцию, которую создал на основании содержимого этого атрибута) console.log($element.onclick); </script>
Т.е., по сути, задание свойства через атрибут – это просто способ инициализации обработчика. Т.к. сам обработчик в этом случае тоже хранится в свойстве DOM-объекта.
Но установка обработчика через свойство имеет недостаток. С помощью него нельзя назначить одному событию несколько обработчиков. Если в коде создадим новый обработчик, то он перезапишет существующий:
<button id="btn" type="button">Кнопка</button> <script> const $element = document.querySelector('#btn'); $element.onclick = function () { alert(`id = ${this.id}`); } // заменит предыдущий обработчик $element.onclick = function () { alert(`text = ${this.textContent}`); } </script>
Кстати, также не получится назначить несколько обработчиков, один через атрибут, а другой через свойство. Последний перепишет предыдущий.
<button id="btn" type="button" onclick="alert(`id = ${this.id}`);">Кнопка</button> <script> const $element = document.querySelector('#btn'); // заменит обработчик, инициализированный с помощью атрибута $element.onclick = function () { alert(`text = ${this.textContent}`); } </script>
Движение мыши
Каждый раз, когда перемещается курсов мыши, срабатывает событие «mousemove» из набора JavaScript mouse events. Оно может быть использовано для отслеживания положения мыши. Это применяется при реализации возможности перетаскивания элементов мышью.
В следующем примере программа выводит на экран панель и устанавливает обработчики событий таким образом, что при перетаскивании эта панель становится уже или шире:
<p>Потяните за край панели, чтобы изменить ее ширину:</p> <div style="background: orange; width: 60px; height: 20px"> </div> <script> var lastX; // Отслеживает последнюю позицию X мыши var rect = document.querySelector("div"); rect.addEventListener("mousedown", function(event) { if (event.which == 1) { lastX = event.pageX; addEventListener("mousemove", moved); event.preventDefault(); // Предотвращает выделение } }); function buttonPressed(event) { if (event.buttons == null) return event.which != 0; else return event.buttons != 0; } function moved(event) { if (!buttonPressed(event)) { removeEventListener("mousemove", moved); } else { var dist = event.pageX - lastX; var newWidth = Math.max(10, rect.offsetWidth + dist); rect.style.width = newWidth + "px"; lastX = event.pageX; } } </script>
Обратите внимание, что обработчик «mousemove» зарегистрирован для всего окна. Даже если во время изменения размеров мышь выходит за пределы панели, мы все равно обновляем ширину панели и прекращаем JavaScript touch events, когда клавиша мыши была отпущена
Мы должны прекратить изменение размера панели, когда пользователь отпускает клавишу мыши. К сожалению, не все браузеры устанавливают для событий «mousemove» свойство which. Существует стандартное свойство buttons, которое предоставляет аналогичную информацию, но оно также поддерживается не во всех браузерах. К счастью, все основные браузеры поддерживают что-то одно: либо buttons, либо which. Функция buttonPressed в приведенном выше примере сначала пытается использовать свойство buttons, и, если оно не доступно, переходит к which.
Когда курсор мыши наводится или покидает узел, запускаются события «mouseover» или «mouseout«. Они могут использоваться для создания эффектов при наведении курсора мыши, вывода какой-нибудь подписи или изменения стиля элемента.
Чтобы создать такой эффект, недостаточно просто начать его отображение при возникновении события «mouseover» и завершить после события «mouseout«. Когда мышь перемещается от узла к одному из его дочерних элементов, для родительского узла происходит событие «mouseout«. Хотя указатель мыши не покинул диапазон распространения узла.
Что еще хуже, эти JavaScript event распространяются так же, как и другие события. Когда мышь покидает один из дочерних узлов, для которого зарегистрирован обработчик, возникнет событие «mouseout«.
Чтобы обойти эту проблему, можно использовать свойство объекта события relatedTarget. В случае возникновения события «mouseover» оно указывает, на какой элемент был наведен курсор мыши до этого. А в случае возникновения «mouseout» — к какому элементу перемещается указатель. Мы будем изменять эффект наведения мыши только, когда relatedTarget находится вне нашего целевого узла.
В этом случае мы изменяем поведение, потому что курсор мыши был наведен на узел из-за его пределов (или наоборот):
<p>Наведите курсор мыши на этот <strong>абзац</strong>.</p> <script> var para = document.querySelector("p"); function isInside(node, target) { for (; node != null; node = node.parentNode) if (node == target) return true; } para.addEventListener("mouseover", function(event) { if (!isInside(event.relatedTarget, para)) para.style.color = "red"; }); para.addEventListener("mouseout", function(event) { if (!isInside(event.relatedTarget, para)) para.style.color = ""; }); </script>
Функция isInside отслеживает родительские связи заданного узла или пока не будет достигнута верхняя часть документа (когда node равен нулю). Либо не будет найден родительский элемент, который нам нужен.
Эффект наведения гораздо проще создать с помощью псевдоселектора CSS :hover, как показано в следующем примере. Но когда эффект наведения предполагает что-то более сложное, чем просто изменение стиля целевого узла, тогда нужно использовать прием с использованием событий «mouseover» и «mouseout» (JavaScript mouse events):
<style> p:hover { color: red } </style> <p>Наведите курсор мыши на этот <strong>абзац</strong>.</p>
Event Bubbling or Event Capturing?
Capturing and bubbling are the two ways of propagating HTML DOM events.
Event propagation defines the element order when an event occurs. For example, when you have an element in a , and the element is clicked, which click event will have to be handled first?
In the case of bubbling, the element that is on the lowest level event is handled first, and the outer ones afterwards. For example, the click event on , and the click event on after.
This order is reversed in the case of capturing the click event on is handled first, then the click event on .
When using the JavaScript method you may set which propagation method will be used with the parameter.
By default, this parameter is set to , meaning that bubbling will be used, and only uses capturing if this value is manually set to .
Example Copy
Свойства
- Только для чтения
- Логическое значение, указывающее, всплыло ли событие вверх по DOM или нет.
- (en-US)
- Историческое название синонима . Если установить значение в до возврата из обработчика события (Event Handler), то событие не будет распространяться дальше (например, на обработчики для родительских узлов).
- Только для чтения
- Логическое значение, показывающее возможность отмены события.
- (en-US) Только для чтения
- Логическое значение, показывающее может или нет событие всплывать через границы между shadow DOM (внутренний DOM конкретного элемента) и обычного DOM документа.
- Только для чтения
- Ссылка на текущий зарегистрированный объект, на котором обрабатывается событие. Это объект, которому планируется отправка события; поведение можно изменить с использованием перенаправления (retargeting).
- (en-US)
- DOM-, через которые всплывало событие.
- Только для чтения
- Показывает, была ли для события вызвана функция .
- Только для чтения
- Указывает фазу процесса обработки события.
- (en-US) Только для чтения
- Явный первоначальный целевой объект события (Mozilla-специфичный). Не может содержать анонимного контента.
- (en-US) Только для чтения
- Первоначальный целевой объект события до перенаправлений (Mozilla-специфичный). Может быть из анонимного контента.
- (en-US)
- Нестандартная альтернатива (оставшаяся от старых версий Microsoft Internet Explorer) для и .
- (en-US) Только для чтения Этот API вышел из употребления и его работа больше не гарантируется.
- , показывающее всплывает ли данное событие через shadow root (внутренний DOM элемента). Это свойство было переименовано в (en-US).
- Нестандартный синоним (остался от старых версий Microsoft Internet Explorer) для .
- Только для чтения
- Ссылка на целевой объект, на котором произошло событие.
- Только для чтения
- Время, когда событие было создано (в миллисекундах). По спецификации это время от начала Эпохи (Unix Epoch), но в действительности в разных браузерах определяется по-разному; кроме того, ведётся работа по изменению его на тип.
- Только для чтения
- Название события (без учёта регистра символов).
- Только для чтения
- Показывает было или нет событие инициировано браузером (например, по клику мышью) или из скрипта (например, через функцию создания события, такую как event.initEvent)
Серия удачных событий
При возникновении события система генерирует сигнал, а также предоставляет механизм, с помощью которого можно автоматически предпринимать какие-либо действия (например, выполнить определённый код), когда происходит событие. Например, в аэропорту, когда взлётно-посадочная полоса свободна для взлёта самолёта, сигнал передаётся пилоту, и в результате они приступают к взлёту.
В Web события запускаются внутри окна браузера и, как правило, прикрепляются к конкретному элементу, который в нем находится. Это может быть один элемент, набор элементов, документ HTML, загруженный на текущей вкладке, или все окно браузера. Существует множество различных видов событий, которые могут произойти, например:
- Пользователь кликает мышью или наводит курсор на определённый элемент.
- Пользователь нажимает клавишу на клавиатуре.
- Пользователь изменяет размер или закрывает окно браузера.
- Завершение загрузки веб-страницы.
- Отправка данных через формы.
- Воспроизведение видео, пауза или завершение воспроизведения.
- Произошла ошибка.
Подробнее о событиях можно посмотреть в Справочнике по событиям.
Каждое доступное событие имеет обработчик событий — блок кода (обычно это функция JavaScript, вводимая вами в качестве разработчика), который будет запускаться при срабатывании события. Когда такой блок кода определён на запуск в ответ на возникновение события, мы говорим, что мы регистрируем обработчик событий
Обратите внимание, что обработчики событий иногда называют слушателями событий (от англ. event listeners). Они в значительной степени взаимозаменяемы для наших целей, хотя, строго говоря, они работают вместе
Слушатель отслеживает событие, а обработчик — это код, который запускается в ответ на событие.
Примечание: Важно отметить, что веб-события не являются частью основного языка JavaScript. Они определены как часть JavaScript-API, встроенных в браузер. Рассмотрим простой пример
Вы уже видели события и обработчики событий во многих примерах в этом курсе, но давайте повторим для закрепления информации. В этом примере у нас есть кнопка , при нажатии которой цвет фона изменяется случайным образом:
Рассмотрим простой пример. Вы уже видели события и обработчики событий во многих примерах в этом курсе, но давайте повторим для закрепления информации. В этом примере у нас есть кнопка , при нажатии которой цвет фона изменяется случайным образом:
JavaScript выглядит так:
В этом коде мы сохраняем ссылку на кнопку внутри переменной типа , используя функцию . Мы также определяем функцию, которая возвращает случайное число. Третья часть кода — . Переменная указывает на элемент — для этого типа объекта существуют возникающие при определённом взаимодействии с ним события, а значит, возможно использование обработчиков событий. Мы отслеживаем момент возникновения события при щелчке мышью, связывая свойство обработчика события с анонимной функцией, генерирующей случайный цвет RGB и устанавливающей его в качестве цвета фона элемента .
Этот код теперь будет запускаться всякий раз, когда возникает событие при нажатии на элемент — всякий раз, когда пользователь щёлкает по нему.
Пример вывода выглядит следующим образом:
События, как понятие, относятся не только к JavaScript — большинство языков программирования имеют модель событий, способ работы которой часто отличается от модели в JavaScript. Фактически, даже модель событий в JavaScript для веб-страниц отличается от модели событий для просто JavaScript, поскольку используются они в разных средах.
Например, Node.js — очень популярная среда исполнения JavaScript, которая позволяет разработчикам использовать JavaScript для создания сетевых и серверных приложений. Модель событий Node.js основана на том, что существуют обработчики, отслеживающие события, и эмиттеры (передатчики), которые периодически генерируют события. В общем-то, это похоже на модель событий в JavaScript для веб-страниц, но код совсем другой. В этой модели используется функция для регистрации обработчиков событий, и функция для регистрации обработчика событий, который отключается после первого срабатывания. Хорошим примером использования являются протоколы событий .
Вы также можете использовать JavaScript для создания кросс-браузерных расширений — улучшения функциональности браузера с помощью технологии WebExtensions. В отличии от модели веб-событий здесь свойства обработчиков событий пишутся в так называемом регистре CamelCase (например, , а не ) и должны сочетаться с функцией . См. для примера.
На данном этапе обучения вам не нужно особо разбираться в различных средах программирования, однако важно понимать, что принцип работы событий в них отличается
The addEventListener() method
Example
Add an event listener that fires when a user clicks a button:
document.getElementById(«myBtn»).addEventListener(«click», displayDate);
The method attaches an event handler to the specified element.
The method attaches an event handler to an element without overwriting existing event handlers.
You can add many event handlers to one element.
You can add many event handlers of the same type to one element, i.e two «click» events.
You can add event listeners to any DOM object not only HTML elements. i.e the window object.
The method makes it easier to control how the event reacts to bubbling.
When using the method, the JavaScript is separated from the HTML markup, for better readability
and allows you to add event listeners even when you do not control the HTML markup.
You can easily remove an event listener by using the method.
Частые ошибки
Если вы только начинаете работать с событиями, обратите внимание на следующие моменты. Функция должна быть присвоена как , а не
Функция должна быть присвоена как , а не .
Если добавить скобки, то – это уже вызов функции, результат которого (равный , так как функция ничего не возвращает) будет присвоен . Так что это не будет работать.
…А вот в разметке, в отличие от свойства, скобки нужны:
Это различие просто объяснить. При создании обработчика браузером из атрибута, он автоматически создаёт функцию с телом из значения атрибута: .
Так что разметка генерирует такое свойство:
Используйте именно функции, а не строки.
Назначение обработчика строкой также сработает. Это сделано из соображений совместимости, но делать так не рекомендуется.
Не используйте для обработчиков.
Такой вызов работать не будет:
Регистр DOM-свойства имеет значение.
Используйте , а не , потому что DOM-свойства чувствительны к регистру.
Вывод
Это все, что вам нужно знать о веб-событиях на этом этапе. Как уже упоминалось, события не являются частью основного JavaScript — они определены в веб-интерфейсах браузера (Web API).
Кроме того, важно понимать, что различные контексты, в которых используется JavaScript, обычно имеют разные модели событий — от веб-API до других областей, таких как браузерные WebExtensions и Node.js (серверный JavaScript). Может сейчас вы не особо в этом разбираетесь, но по мере изучения веб-разработки начнёт приходить более ясное понимание тематики
Если у вас возникли вопросы, попробуйте прочесть статью снова или .