August 17th, 2021

JavaScript: событие mouseup за пределами HTML-элемента

В предыдущем посте при решении задачи, затрагивающей события мыши и механизм «Drag’n’Drop» (перетаскивания) в браузерах, у меня появилось несколько вопросов.

Например, насчет события мыши mouseup. Оно возникает при отпускании ранее нажатой кнопки мыши (или от другого указательного устройства, которое можно использовать для передвижения курсора мыши; по-английски такое устройство называют «pointing device», например: трекбол, тачпад, световое перо, графический планшет, сенсорный экран и так далее).

Я уже упоминал ранее, что правила работы браузера с событиями мыши определены в спецификации «UI Events». О событии mouseup в этой спецификации написано по следующим ссылкам:

https://www.w3.org/TR/uievents/#event-type-mouseup
https://w3c.github.io/uievents/#event-type-mouseup

Там сказано, цитирую:

A user agent MUST dispatch this event when a pointing device button is released over an element.


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

Я обратил внимание на слова «над элементом». Однако, на практике, для моего браузера («Microsoft Edge» на движке «Chromium») это не всегда верно.

Для тестирования я написал такой скрипт на языке JavaScript:
let elem;

// elem = window;                     // окно браузера
// elem = document;                   // HTML-страница (документ)
// elem = document.documentElement;   // HTML-элемент <html>
// elem = document.body;              // HTML-элемент <body>
elem = document.querySelector("div"); // HTML-элемент <div>

elem.addEventListener("mousedown", onMouse);
elem.addEventListener("mouseup", onMouse);

function onMouse(event) {
    console.log(event.type + ", " + event.target);
}

Меняя значение переменной elem, можно проверить работу событий мыши mousedown и mouseup на разных объектах DOM (объектной модели документа), а также на объекте window окна браузера (как я понимаю, в иерархии объектов окно браузера находится выше документа, открытого в браузере).

Если просто нажимать и отпускать кнопку мыши над HTML-элементами, HTML-страницей или окном браузера, если на них повешены события мыши mousedown и mouseup, то всё происходит так, как написано в спецификации.

Однако, если навести курсор мыши на объект elem, нажать кнопку мыши (сгенерируется событие mousedown), а затем, не отпуская кнопку мыши, вывести курсор мыши за пределы объекта elem и уже там отпустить кнопку мыши, то при такой последовательности действий пользователя браузер сработает по-разному в зависимости от объекта в переменной elem. (Описанные действия пользователь обычно выполняет, когда хочет перетащить объект с помощью механизма «Drag’n’Drop».)

Для HTML-элементов div и body при вышеописанном порядке действий отпускание кнопки мыши за пределами этих HTML-элементов не влечет срабатывания функции-обработчика onMouse, повешенной на событие mouseup для указанных HTML-элементов. То есть, как я понимаю, это соответствует спецификации (событие mouseup происходит не над целевыми HTML-элементами, поэтому на них и не отлавливается).

А вот для HTML-элемента html, для объекта document (документ, загруженный браузером) и для объекта window (окно браузера) в той же ситуации отпускание кнопки мыши за пределами этих объектов влечет срабатывание функции-обработчика onMouse, повешенной на событие mouseup для указанных объектов. То есть, как я понимаю, это не соответствует спецификации (событие mouseup может происходить вообще за пределами окна браузера, но всё равно отлавливается).

Насчет этого момента в спецификации сделано замечание (по-английски «note», замечания в спецификации маркированы зеленым цветом фона):

In some implementation environments, such as a browser, a mouseup event can be dispatched even if the pointing device has left the boundary of the user agent, e.g., if the user began a drag operation with a mouse button pressed.


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

Получается, этот момент в спецификации оговорен, хоть там и не сказано конкретно, что данное исключение может действовать только на HTML-элемент html, объект document и объект window. Видимо, это решают разработчики конкретного браузера или другой подобной программы, реализующей данную спецификацию.