ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Category:

JavaScript: mousedown, preventDefault, iframe

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

Причиной разбора в этом посте послужило непонятное для меня поведение браузера (у меня — «Microsoft Edge» на движке «Chromium») при решении задачи «Слайдер» к подразделу 3.3 «Drag'n'Drop с событиями мыши» второй части учебника по JavaScript. Я посветил разбору этой задачи отдельный пост (см. окончание этого поста).

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

Для тестов я использовал такой скрипт на языке 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);
}

К вышеупомянутой задаче про слайдер авторы задачи в своем решении использовали в начале функции-обработчика события mousedown следующий код:
event.preventDefault(); // предотвратить запуск выделения (действие браузера)

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

В данном же случае мы не хотим, чтобы браузер начал действие по умолчанию (выделение кусочка текста на HTML-странице) по событию мыши mousedown, поэтому и применяется метод event.preventDefault, который отменяет действие браузера по умолчанию.

Это прекрасно работает, пока в дело не вступает HTML-элемент iframe. Вообще, в вышеупомянутой задаче про слайдер HTML-элемент iframe отсутствует. Я решил эту задачу и не испытал никаких проблем. Однако, авторы обсуждаемого учебника по JavaScript придумали показывать работу своего решения задачи про слайдер, вставив HTML-страницу с решением задачи на HTML-страницу с постановкой задачи с помощью HTML-элемента iframe (они это проделали не только в этой задаче, но и в других). Тут-то в дело и вступил этот HTML-элемент.

Итак, сначала я изменил вышеприведенный тестовый скрипт, вставив в функцию-обработчик onMouse вызов метода event.preventDefault для события mousedown:
function onMouse(event) {
    if (event.type == "mousedown") {
        event.preventDefault();
    }
    console.log(event.type + ", " + event.target);
}

Поведение браузера осталось таким же, какое было описано в предыдущем посте.

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

У меня уже имелась тестовая HTML-страница test.html, в теле которой содержался один HTML-элемент div. Я создал еще одну тестовую HTML-страницу, которую назвал test1.html. В ее тело я вставил HTML-элемент iframe с первоначальной тестовой HTML-страницей:
<iframe style="height: 200px; width: 100%" src="test.html"></iframe>
Дальнейшие тесты я уже проводил на этой новой HTML-странице test1.html.

В случае отсутствия запуска метода event.preventDefault поведение браузера (даже и с наличием HTML-элемента iframe) остается таким же, какое было описано в предыдущем посте.

В случае же присутствия запуска метода event.preventDefault (при наличии HTML-элемента iframe) поведение браузера поменялось.

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

Это поведение можно увидеть и на странице вышеупомянутой задачи со слайдером, поэкспериментировав с демонстрационным примером. Схватив бегунок слайдера мышкой, можно (не отпуская кнопку мыши) вывести курсор мыши за пределы встроенного кадра (HTML-элемента iframe) и отпустить кнопку мыши. Если после этого снова ввести курсор мыши в пределы встроенного кадра, станет видно, что скрипт не отловил отпускания кнопки мыши, потому что хоть кнопка мыши уже и отпущена, бегунок слайдера продолжает следовать за курсором мыши. Очевидно, авторы задачи и решения к ней не рассчитывали на такое поведение слайдера.

По идее, наличие метода event.preventDefault в функции-обработчике события mousedown не должно вызывать такое изменение поведения браузера.

Я поискал в интернете и нашел следующее сообщение о баге в адрес разработчиков движка «Chromium» (там в точности описана данная ситуация):

https://bugs.chromium.org/p/chromium/issues/detail?id=269917

Сообщение называется «Issue 269917: preventDefault on mousedown prevents proper handling of mouse events in iframes» и дата его первоначальной подачи — аж 8 августа 2013 года, то есть более 8 лет назад. Обсуждение этого сообщения длится по сей день (последнее сообщение в обсуждении датировано 15 июня этого года).

А что же делать, если нам нужно предотвратить выделение текста (и других объектов), которое браузер запустит по умолчанию при генерации события mousedown?

На сайте «Stack Overflow» был похожий вопрос. Ответ на него можно увидеть по следующей ссылке (отвечающий также привел и код действующего примера в своем ответе):

https://stackoverflow.com/questions/66703382/how-to-keep-watching-mouse-move-outside-iframe

Там предложено вставить в стиль целевого HTML-элемента указание user-select: none;. Неидеальное решение, но лучше, чем ничего.

В случае задачи со слайдером я просто вообще не стал вызывать метод event.preventDefault, так как на тестовой HTML-странице, заданной в задаче про слайдер, нет ничего, кроме нескольких HTML-элементов div, которые и так не будут автоматически выделяться.
Tags: Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments