July 31st, 2021

JavaScript: тонкости работы с событиями мыши

В процессе чтения подраздела 3.2 «Движение мыши: mouseover/out, mouseenter/leave» второй части учебника по JavaScript я экспериментировал с браузером и сделал несколько выводов, которыми можно дополнить указанный подраздел. Эта информация может пригодиться при решении примеров к этому подразделу.

Я экспериментировал в браузере «Microsoft Edge» на движке «Chromium». В экспериментах я рассматривал не все события мыши, а только mouseover (возникает при заходе курсора мыши на HTML-элемент), mouseout (возникает при уходе курсора мыши с HTML-элемента) и mousemove (возникает при движении курсора мыши над HTML-элементом).

* * *

Вообще правила работы браузера с событиями мыши определены в спецификации, которая сейчас называется «UI Events». Аббревиатура «UI» расшифровывается как «user interaction» (по-русски «взаимодействие с пользователем»). «UI Events» по-русски означает «события при взаимодействии с пользователем» (имеется в виду взаимодействие браузера с пользователем). Эту спецификацию можно почитать по следующим ссылкам:

1. https://www.w3.org/TR/uievents/
2. https://w3c.github.io/uievents/

По первой ссылке сейчас находится спецификация «UI Events» в варианте «W3C Working Draft» (по-русски «рабочий проект», это один из этапов разработки рекомендации (эквивалент стандарта) от организации «W3C», разрабатывающей стандарты для Всемирной паутины) от 30 мая 2019 года.

По второй ссылке сейчас находится спецификация «UI Events» в варианте «Editor’s Draft» от 20 июля 2021 года. «Editor’s Draft» — это, как я понимаю, свежайший снимок спецификации от людей («редакторов»), которые в данный момент занимаются ее дальнейшей разработкой.

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

* * *

В обсуждаемом подразделе учебника меня особенно заинтересовал отдел «Пропуск элементов».

В теории, когда пользователь водит мышью по HTML-странице в области просмотра браузера, генерируются три события, перечисленные выше, которые можно обработать в скрипте на языке JavaScript. Рассматриваемые события генерируются при проведении курсором мыши над конкретным HTML-элементом в порядке mouseover-mousemove-mouseout. При этом события mouseover и mouseout генерируются по одному на HTML-элемент, а событий mousemove над одним HTML-элементом между событиями mouseover и mouseout может быть множество.

Пользователь может водить мышью по HTML-странице с разной скоростью. В учебнике рассказано, что при достаточно высокой скорости проведения указателя мыши над HTML-элементом соответствующие события могут не сгенерироваться (произойдет «пропуск» этого HTML-элемента). В учебнике сказано, что HTML-элемент может быть пропущен только целиком. То есть, если сгенерировалось событие mouseover, то после этого для данного HTML-элемента обязательно будет сгенерировано и событие mouseout.

Я бы добавил еще к этому, что (судя по моим экспериментам):

Вывод 1. Браузер может либо полностью «пропустить» HTML-элемент, либо, как минимум, сгенерировать группу из трех событий mouseover-mousemove-mouseout (каждое из этих событий по одному, событие mousemove может быть в этом случае либо одно, либо их может быть множество). То есть у меня не получилось увидеть ситуацию, когда после захода и ухода указателя мыши с HTML-элемента сгенерировалась бы только пара событий mouseover-mouseout или mouseover-mousemove или mousemove-mouseout.

Следующий вывод может показаться очевидным, но всё же:

Вывод 2. «Пропуск» HTML-элемента более вероятен, если этот элемент маленький или тонкий. Чем больше (толще) HTML-элемент, тем менее вероятен его «пропуск».

Я замерил время между генерацией трех рассматриваемых событий на одном HTML-элементе. Для этого я применил метод Date.now(). Про этот способ можно прочитать в подразделе 5.11 «Дата и время» первой части обсуждаемого учебника.

Вывод 3. Время между рассматриваемыми событиями может быть от нуля миллисекунд (минимальное) и больше. Особенно часто промежуток в ноль миллисекунд бывает между событием mouseover и первым событием mousemove над одним и тем же HTML-элементом. Но промежуток в ноль миллисекунд между mouseover и первым mousemove бывает не всегда.

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

Еще в учебнике сказано, что свойство relatedTarget объекта с информацией о произошедшем событии для трех обсуждаемых событий мыши может быть равно значению null, когда курсор мыши резко перемещается из-за пределов области просмотра браузера на HTML-элемент (для события mouseover) или, наоборот, резко перемещается с HTML-элемента за пределы области просмотра браузера (для события mouseout).

Я бы тут еще добавил, что:

Вывод 4. Для события mousemove свойство relatedTarget объекта с информацией о произошедшем событии всегда равно значению null (об этом сказано в вышеуказанной спецификации).

Я проанализировал координаты указателя курсора мыши в момент генерации событий mouseover и mouseout. Как мне казалось, в момент события захода указателя мыши на HTML-элемент координаты указателя должны быть равны координатам пикселя, находящегося в самом внешнем ряду пикселей границы (border) HTML-элемента. А в момент события ухода указателя мыши с HTML-элемента координаты указателя, как я думал, должны быть равны координатам пикселя, тоже находящегося в самом внешнем ряду пикселей границы (border) HTML-элемента. Но это представление оказалось неверным.

Вывод 5. При событии mouseover координаты указателя мыши равны координатам пикселя, либо находящегося в самом внешнем ряду пикселей границы HTML-элемента, либо находящегося несколько в глубине HTML-элемента. Чем больше скорость движения указателя мыши, тем глубже внутри HTML-элемента находится рассматриваемый пиксель. Иллюстрация:



Я сделал снимок области просмотра браузера и увеличил его в 4 раза в графическом редакторе, чтобы можно было видеть ситуацию попиксельно. На иллюстрации видно кусочек (левый верхний угол) HTML-элемента div. У него красная граница (border) толщиной в 15 пикселей, а клиентская часть этого HTML-документа (внутренние отступы padding плюс содержимое HTML-элемента) выкрашена в серый цвет. Черной пиксельной линией я изобразил движение указателя мыши (серой стрелкой показано направление движения указателя мыши, сверху вниз, на HTML-элемент). Желтым цветом отмечены пиксели, в которых может быть сгенерировано событие mouseover в зависимости от скорости движения указателя мыши.

Вывод 6. При событии mouseout координаты указателя мыши равны координатам пикселя, либо находящегося в самом внешнем ряду пикселей границы HTML-элемента, либо уже находящегося на каком-то расстоянии за пределами HTML-элемента. Иллюстрация:



Эта иллюстрация сделана по тому же принципу, что и предыдущая. На ней видно кусочек (левый нижний угол) HTML-элемента div. Черной пиксельной линией изображено движение указателя мыши (белой стрелкой показано направление движения указателя мыши, сверху вниз, из HTML-элемента). Желтым цветом отмечены пиксели, в которых может быть сгенерировано событие mouseout в зависимости от скорости движения указателя мыши.

Можно сказать, что в данном случае у браузера как-будто есть некая инерция, события несколько «запаздывают», из-за чего функция, обрабатывающая событие, получает несколько смещенные координаты.

VLDL, оплата монетами

На «Youtube» есть канал очень популярной англоязычной группы «VLDL» (расшифровывается как «Viva La Dirt League»), снимающей юмористические скетчи:

https://www.youtube.com/channel/UCchBatdUMZoMfJ3rIzgV84g
https://www.vivaladirtleague.com

Кстати, рекомендую, у них очень забавные ролики!

На «Youtube» также есть множество каналов (я знаю не меньше десятка), которые занимаются переводом скетчей «VLDL» на русский язык. Недавно на одном из таких каналов я посмотрел скетч «Оплата монетами» (в оригинале «Paying with coins»):

https://www.youtube.com/watch?v=1GHtPaq_IUc (перевод на русский)
https://www.youtube.com/watch?v=tTx-BaBuZ6w (оригинал)

Там покупатель в магазине решил потроллить продавца и оплатить покупку наличными в монетах на сумму в 150 долларов. Вроде как такую кучу монет долго считать, и поэтому продавец в шоке.



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

В ролике покупатель дополнительно к куче монет издевательски передает продавцу еще 15 монет как 1 процент от суммы оплаты в качестве чаевых.

На вид вся куча монет состоит из одинаковых монет. 1 процент от суммы оплаты составляет 1,5 доллара или 150 центов. Если 150 центов состоят из 15 монет, значит номинал одной монеты — десять центов. То есть вся куча монет состоит из десятицентовиков.

Таким образом, в куче одинаковых монет из ролика на сумму в 150 долларов должно содержаться 1500 монет номиналом в десять центов. Если считать вручную и на подсчет каждой монеты тратить 1-2 секунды, то подсчитать 1500 монет получится за 25-50 минут. Если же у продавца есть весы, то можно взвесить сразу все монеты и разделить на вес одной монеты. С весами всё прошло бы быстро, но, видимо, у продавца нет весов.

Пока я смотрел ролик, у меня закрались сомнения, что показанные в ролике монеты являются десятицентовиками. В США десятицентовики называют коротко «даймами» («dime»). Сейчас (с 1946 года) их делают из медно-никелевого сплава, которому никель придаёт серебряный цвет:

https://en.wikipedia.org/wiki/Dime_(United_States_coin)

А монеты в ролике — золотого цвета.

Позже мне напомнили, что доллары и центы бывают не только американские (США). Ребята из группы «VLDL» живут в Новой Зеландии, а там у них свои собственные, новозеландские, доллары и центы.

Новозеландский десятицентовик современного образца изготавливают с 2006 года. Он сделан из стали, покрытой медью. Медное покрытие придает монете золотой цвет.

https://en.wikipedia.org/wiki/Coins_of_the_New_Zealand_dollar
https://en.wikipedia.org/wiki/New_Zealand_ten-cent_coin