Window

Флаги свойств

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

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

Мы ещё не встречали эти атрибуты, потому что обычно они скрыты. Когда мы создаём свойство «обычным способом», все они имеют значение . Но мы можем изменить их в любое время.

Сначала посмотрим, как получить их текущие значения.

Метод Object.getOwnPropertyDescriptor позволяет получить полную информацию о свойстве.

Его синтаксис:

Объект, из которого мы получаем информацию.
Имя свойства.

Возвращаемое значение – это объект, так называемый «дескриптор свойства»: он содержит значение свойства и все его флаги.

Например:

Чтобы изменить флаги, мы можем использовать метод Object.defineProperty.

Его синтаксис:

,
Объект и его свойство, для которого нужно применить дескриптор.
Применяемый дескриптор.

Если свойство существует, обновит его флаги. В противном случае метод создаёт новое свойство с указанным значением и флагами; если какой-либо флаг не указан явно, ему присваивается значение .

Например, здесь создаётся свойство , все флаги которого имеют значение :

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

Теперь давайте рассмотрим на примерах, что нам даёт использование флагов.

Глобальное запечатывание объекта

Дескрипторы свойств работают на уровне конкретных свойств.

Но ещё есть методы, которые ограничивают доступ ко всему объекту:

Object.preventExtensions(obj)
Запрещает добавлять новые свойства в объект.
Object.seal(obj)
Запрещает добавлять/удалять свойства. Устанавливает для всех существующих свойств.
Object.freeze(obj)
Запрещает добавлять/удалять/изменять свойства. Устанавливает для всех существующих свойств.

А также есть методы для их проверки:

Object.isExtensible(obj)
Возвращает , если добавление свойств запрещено, иначе .
Object.isSealed(obj)
Возвращает , если добавление/удаление свойств запрещено и для всех существующих свойств установлено .
Object.isFrozen(obj)
Возвращает , если добавление/удаление/изменение свойств запрещено, и для всех текущих свойств установлено .

На практике эти методы используются редко.

Распространение

Обработчики событий (например, JavaScript touch events), зарегистрированные для родительских узлов, также будут принимать события, которые происходят в дочерних элементах. Если была нажата кнопка, находящаяся внутри абзаца, обработчики событий абзаца также получат событие click.

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

В любой момент обработчик события может вызвать для объекта события метод stopPropagation, чтобы предотвратить распространение события дальше. Это может быть полезно, когда у вас есть кнопка внутри другого интерактивного элемента, и вы не хотите, чтобы при нажатии кнопки активировалось поведение, заданное для клика мышью по внешним элементам.

В следующем примере мы регистрируем обработчики «MouseDown» как для кнопки, так и для абзаца. При клике правой клавишей (JavaScript mouse events) обработчик вызывает метод stopPropagation, который предотвращает запуск обработчика абзаца. При нажатии на кнопку другой клавишей мыши запускаются оба обработчика:

<p>Абзац и в нем <button>кнопка</button>.</p>
<script>
  var para = document.querySelector("p");
  var button = document.querySelector("button");
  para.addEventListener("mousedown", function() {
    console.log("Handler for paragraph.");
  });
  button.addEventListener("mousedown", function(event) {
    console.log("Handler for button.");
    if (event.which == 3)
      event.stopPropagation();
  });
</script>

Большинство объектов событий имеют свойство target, которое указывает на узел, в котором они возникли. Вы можете использовать это свойство, чтобы случайно не обрабатывать какое-то событие, которое распространяется вверх из узла.

Также можно использовать JavaScript event target, чтобы расширить диапазон события определенного типа. Например, если у вас есть узел, содержащий длинный список кнопок, более удобно зарегистрировать один обработчик клика для внешнего узла и использовать свойство target, чтобы отслеживать, была ли нажата кнопка, а не регистрировать экземпляры обработчика для всех кнопок:

<button>A</button>
<button>B</button>
<button>C</button>
<script>
  document.body.addEventListener("click", function(event) {
    if (event.target.nodeName == "BUTTON")
      console.log("Clicked", event.target.textContent);
  });
</script>

Всплытие

Основной принцип всплытия:

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

Например, есть 3 вложенных элемента , с обработчиком на каждом:

Всплытие гарантирует, что клик по внутреннему вызовет обработчик (если есть) сначала на самом , затем на элементе далее на элементе , и так далее вверх по цепочке родителей до самого .

Поэтому если в примере выше кликнуть на , то последовательно выведутся : → → .

Этот процесс называется всплытием, потому что события «всплывают» от внутреннего элемента вверх через родителей, подобно тому, как всплывает пузырёк воздуха в воде.

Всплывают почти все события.

Ключевое слово в этой фразе – «почти».

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

Погружение

В современном стандарте, кроме «всплытия» событий, предусмотрено ещё и «погружение».

Оно гораздо менее востребовано, но иногда, очень редко, знание о нём может быть полезным.

Строго говоря, стандарт выделяет целых три стадии прохода события:

  1. Событие сначала идёт сверху вниз. Эта стадия называется «стадия перехвата» (capturing stage).
  2. Событие достигло целевого элемента. Это – «стадия цели» (target stage).
  3. После этого событие начинает всплывать. Это – «стадия всплытия» (bubbling stage).

В стандарте DOM Events 3 это продемонстрировано так:

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

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

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

Чтобы поймать событие на стадии перехвата, нужно использовать третий аргумент :

  • Если аргумент , то событие будет перехвачено по дороге вниз.
  • Если аргумент , то событие будет поймано при всплытии.

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

Есть события, которые не всплывают, но которые можно перехватить

Бывают события, которые можно поймать только на стадии перехвата, а на стадии всплытия – нельзя…

Например, таково событие фокусировки на элементе onfocus. Конечно, это большая редкость, такое исключение существует по историческим причинам.

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

Если вы только начинаете работать с событиями, обратите внимание на следующие моменты. Функция должна быть присвоена как , а не

Функция должна быть присвоена как , а не .

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

…А вот в разметке, в отличие от свойства, скобки нужны:

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

Так что разметка генерирует такое свойство:

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

Назначение обработчика строкой также сработает. Это сделано из соображений совместимости, но делать так не рекомендуется.

Не используйте для обработчиков.

Такой вызов работать не будет:

Регистр DOM-свойства имеет значение.

Используйте , а не , потому что DOM-свойства чувствительны к регистру.

Добавление обработчика через свойство 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>

Event object

To properly handle an event we’d want to know more about what’s happened. Not just a “click” or a “keydown”, but what were the pointer coordinates? Which key was pressed? And so on.

When an event happens, the browser creates an event object, puts details into it and passes it as an argument to the handler.

Here’s an example of getting pointer coordinates from the event object:

Some properties of object:

Event type, here it’s .
Element that handled the event. That’s exactly the same as , unless the handler is an arrow function, or its is bound to something else, then we can get the element from .
Window-relative coordinates of the cursor, for pointer events.

There are more properties. Many of them depend on the event type: keyboard events have one set of properties, pointer events – another one, we’ll study them later when we come to different events in details.

The event object is also available in HTML handlers

If we assign a handler in HTML, we can also use the object, like this:

That’s possible because when the browser reads the attribute, it creates a handler like this: . That is: its first argument is called , and the body is taken from the attribute.

Event Bubbling (Всплытие событий)

Если вы хотите завершить чтение на этом моменте — то смело это делайте. Мы уже узнали основы делегирования событий. Но для глубокого понимания того, почему оно работает, нам нужно понять Event Bubbling.

Что происходит на самом деле при клике?

Всякий раз, когда пользователь кликает, этот самый клик отдаётся вверх на самую высь DOM и отрабатывает событие клика на всех элементах родителя по которому был сделан клик. Вы не всегда видите эти клики, так как вы не всегда слушаете (с ) клики на этих элементах, но как бы то ни было, но такое “всплытие” действий имеет место быть.

Это называется Event Bubbling (Всплытие событий) или распространением события.

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

Вот пример:

Выше у нас есть три дива: DIV #1, DIV #2, DIV #3. У каждого ’а есть свой прослушиватель событий и когда мы на него кликаем в браузере, то мы выводим в консоль имя класса, через функцию .

Выше мы видим то, что мы бы увидели в браузере

Обратите внимание на то, как мышка кликает по третьему ’у. Как и ожидалось, когда я кликаю по нему, я вижу его класс в консоле

Но кликая по div #3, я также кликаю и по div #2 и div#1, который вывелись в консоль. Это и называется всплытием событий. Мы видим каждый класс, потому что мы добавили прослушиватель событий каждому родительскому ’у.

Но вернемся к нашему примеру с делегированием события:

Вернёмся к нашему примеру с делегированием событий — у нас был только один слушатель события и он был выставлен на неупорядоченный список с классом . Тем не менее, мы кликнули на потомка этого родительского HTML элемента, элемент input, который запустил прослушиватель событий, который привязали к списку.

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

Делегирование событий

Именно оно решает эту проблему. Чтобы понять принцип его работы, нам надо посмотреть ниже на список персонажей Disney.

У этого списка есть довольно простой функционал. Именно для наших нужд мы можем добавить несколько персонажей в этот список и проверить боксы рядом с именем персонажа.

Этот список также является динамическим. Инпуты (Mickey, Minnie, Goofy) были добавлены уже ПОСЛЕ загрузки страницы и следовательно, на них не были прикреплены слушатели событий.

Давайте посмотрим на этот код:

Но давайте посмотрим на HTML при загрузке страницы:

А теперь давайте взглянем на HTML после загрузки страницы (из локального веб-хранилища, API запроса и т.п.):

Если вы захотите кликнуть на инпуты персонажей — Mickey, Minnie, or Goody), то вы наверное ожидали бы увидеть всплывающее окно с надписью “hi!”, но так как они не были загружены на страницу при её инициализации, то и прослушиватели событий НЕ БЫЛИ добавлены на эти элементы и само собой ничего не произойдёт.

Действия по умолчанию

Со многими событиями связаны действия по умолчанию. Если вы нажмете на ссылку, то перейдете к целевому элементу ссылки. Если нажмете стрелку вниз, браузер прокрутит страницу вниз. Если вы кликните правой клавишей мыши, откроется контекстное меню.

Для большинства типов событий обработчики JavaScript event вызываются до выполнения действий по умолчанию. Если не нужно, чтобы выполнялось поведение по умолчанию, нужно вызвать для объекта события метод preventDefault.

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

<a href="https://developer.mozilla.org/">MDN</a>
<script>
  var link = document.querySelector("a");
  link.addEventListener("click", function(event) {
    console.log("Nope.");
    event.preventDefault();
  });
</script>

Старайтесь не делать так, если у вас нет на это веских причин.

В зависимости от браузера некоторые события не могут быть перехвачены. В Google Chrome, например, сочетание клавиш (event keycode JavaScript) для закрытия текущей вкладки (Ctrl-W или Command-W) не может быть обработано с помощью JavaScript.

Синтаксис

Чувствительная к регистру строка, представляющая тип обрабатываемого события.
Объект, который принимает уведомление, когда событие указанного типа произошло. Это должен быть объект, реализующий интерфейс  или просто функция JavaScript.
 Необязательный
Объект , который определяет характеристики объекта, прослушивающего событие. Доступны следующие варианты:

  • :   указывает, что события этого типа будут отправлены зарегистрированному обработчику  перед отправкой на , расположенный ниже в дереве DOM.
  • : указывает, что обработчик должен быть вызван не более одного раза после добавления. Если , обработчик автоматически удаляется при вызове.
  • :   указывает, что обработчик никогда не вызовет . Если всё же вызов будет произведён, браузер должен игнорировать его и генерировать консольное предупреждение. Пример
  • : указывает, что обработчик должен быть добавлен в системную группу. Доступно только в коде, запущенном в XBL или в расширении Chrome.
Необязательный

Если равно , указывает, что пользователь желает начать захват. После инициализации захвата все события указанного типа будут отправлены в зарегистрированный  перед отправкой в какой-либо  под ним в дереве DOM. События, восходящие вверх по дереву, не будут вызывать обработчиков, которым назначено использовать захват. Смотрите  для более детального объяснения. Значение  по умолчанию равно .

Note: Для обработчиков событий прикреплённых к цели события, событие  находиться в целевой фазе, а не в фазах захвата или всплытия. События в целевой фазе инициируют все обработчики на элементе в том порядке, в котором они были зарегистрированы независимо от параметра .

Note:  не всегда был опциональным. Лучше указывать данный параметр для повышения совместимости.

Если равно , обработчик будет получать сгенерированные события, посланные со страницы (по умолчанию равно  для chrome и  для обычных веб-страниц). Этот параметр доступен только в Gecko и в основном полезен для использования в дополнениях и самом браузере. Смотрите Взаимодействие между привилегированными и непривилегированными страницами для примеров использования.

Прежде чем использовать определённое значение в объекте , рекомендуется убедиться, что браузер пользователя поддерживает его, поскольку это дополнение, которое не все браузеры поддерживали исторически.

Несколько обработчиков одного и того же события

В javascript можно назначать НЕСКОЛЬКО обработчиков одного и того же события. Для этого используются методы:

addEventListener — добавление обработчика

removeEventListener — удаление обработчика

Пример: Щелкнув по кнопке вызвать диалоговое окно со словом Ура!. Выполнить задание, используя метод addEventListener.

Решение: 

  • html код:
<input type="button" value="кнопка" id="MyElem">

скрипт:

<script type="text/javaScript"> 
function message() {
	alert('Ура!');
}
var input = document.getElementById("MyElem"); 
input.addEventListener("click", message);
</script>

Пример: В предыдущем задании удалить добавленный обработчик с кнопки, используя метод removeEventListener.

Решение: 

<script type="text/javaScript"> 
function message() {
	alert('Ура!');
}
var input = document.getElementById("MyElem"); 
input.addEventListener("click", message);
input.removeEventListener("click", message);
</script>

Всплытие и погружение события

obj.onevent = function(e) {/*...*/}
// где e - объект события
// e.target - элемент, на котором произошло событие

Прекращение всплытия

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

Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие.

Для остановки всплытия нужно вызвать метод .

Например, здесь при клике на кнопку обработчик не сработает:

event.stopImmediatePropagation()

Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены.

То есть, препятствует продвижению события дальше, но на текущем элементе все обработчики отработают.

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

Не прекращайте всплытие без необходимости!

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

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

Например:

  1. Мы делаем меню. Оно обрабатывает клики на своих элементах и делает для них . Вроде бы, всё работает.
  2. Позже мы решили отслеживать все клики в окне, для какой-то своей функциональности, к примеру, для статистики – где вообще у нас кликают люди. Например, Яндекс.Метрика так делает, если включить соответствующую опцию.
  3. Над областью, где клики убиваются , статистика работать не будет! Получилась «мёртвая зона».

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

Целевой элемент event.target

На каком бы элементе мы ни поймали событие, всегда можно узнать, где конкретно оно произошло.

Самый глубокий элемент, который вызывает событие, называется «целевым» или «исходным» элементом и доступен как .

Отличия от (=):

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

Например, если стоит только один обработчик , то он «поймает» все клики внутри формы. Где бы ни был клик внутри – он всплывёт до элемента , на котором сработает обработчик.

При этом:

  • () всегда будет сама форма, так как обработчик сработал на ней.
  • будет содержать ссылку на конкретный элемент внутри формы, самый вложенный, на котором произошёл клик.

Результат
script.js
example.css
index.html

Возможна и ситуация, когда и – один и тот же элемент, например если в форме нет других тегов и клик был на самом элементе .

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

Событие – это определённый сигнал от браузера. Он сообщает нам о том, что что-то произошло.

Например: щелчок мыши, нажатие клавиши на клавиатуре, изменение размера области просмотра, завершение загрузки документа и т.д.

При этом сигнал всегда связан с объектом. Подавать сигналы могут различные объекты: , , DOM-элементы и т.д.

Список некоторых событий и их название:

  • – завершение загрузки DOM;
  • – клик (нажатие левой кнопки мыши, на устройствах с сенсорным управлением возникает при касании);
  • – нажатие клавиши на клавиатуре;
  • – изменение размеров документа;
  • – окончание изменения значения в поле ввода.

Adding Event Handlers

JavaScript provides you with a lot of different opportunities, which we will now review one by one. First, we will learn to simply add an event handler to an element. Then, we’ll try adding more than one at once.

Once we get the idea, we’ll see how event handlers should be applied to window objects. Carefully review the code examples provided to grasp the concepts.

Adding an Event Handler to an Element

When you add a JavaScript event listener, the function to be executed upon an event can be either anonymous or named. In the example below, you can see the anonymous function used. In this case, you need to define the code in the function:

Example Copy

In the example below you can see the named function being used. In this case, you are referencing an external function:

Example Copy

Add Many Event Handlers to the Same Element

The JavaScript method lets you add multiple event handlers to a single element, while not overwriting any previously assigned event handlers. Take a look at the example below:

Example Copy

Different event types may be specified as well:

Example Copy

Add an Event Handler to the Window Object

The JavaScript method lets you add event listeners to HTML DOM objects, such as HTML elements, the document that the HTML is in itself, the window object, and any other object that supports events (like the xmlHttpRequest object).

Example Copy

Движение мыши

Каждый раз, когда перемещается курсов мыши, срабатывает событие «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>

Summary

There are 3 ways to assign event handlers:

  1. HTML attribute: .
  2. DOM property: .
  3. Methods: to add, to remove.

HTML attributes are used sparingly, because JavaScript in the middle of an HTML tag looks a little bit odd and alien. Also can’t write lots of code in there.

DOM properties are ok to use, but we can’t assign more than one handler of the particular event. In many cases that limitation is not pressing.

The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance and (to be covered). Also supports objects as event handlers. In that case the method is called in case of the event.

No matter how you assign the handler – it gets an event object as the first argument. That object contains the details about what’s happened.

We’ll learn more about events in general and about different types of events in the next chapters.

Итого

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

Базовый конструктор принимает обязательное имя события и – объект с двумя свойствами:

  • чтобы событие всплывало.
  • если мы хотим, чтобы работал.

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

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

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

Весьма часто, когда разработчик хочет сгенерировать встроенное событие – это вызвано «кривой» архитектурой кода.

Как правило, генерация встроенных событий полезна в следующих случаях:

  • Либо как явный и грубый хак, чтобы заставить работать сторонние библиотеки, в которых не предусмотрены другие средства взаимодействия.
  • Либо для автоматического тестирования, чтобы скриптом «нажать на кнопку» и посмотреть, произошло ли нужное действие.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector