August 3rd, 2021

JavaScript: умная подсказка, разбор постановки задачи

Решил задачу «"Умная" подсказка» к подразделу 3.2 «Движение мыши: mouseover/out, mouseenter/leave» второй части учебника по JavaScript.

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

Задача осложняется еще и тем, что к ней дано пять автоматических тестов, реализованных с помощью фреймворка «Mocha» (о нем и о его использовании рассказывалось в подразделе 3.5 «Автоматическое тестирование c использованием фреймворка Mocha» первой части обсуждаемого учебника).

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

* * *

Цитата:

Напишите функцию, которая показывает подсказку над элементом только в случае, когда пользователь передвигает мышь на него, но не через него.

Другими словами, если пользователь подвинул курсор на элементе и остановился – показывать подсказку. А если он просто быстро провёл курсором по элементу, то не надо ничего показывать. Кому понравится лишнее мелькание?


Во-первых, позже выяснится, что требуется написать не одну-единственную функцию, а требуется дописать на языке JavaScript класс HoverIntent, код которого находится в отдельном файле hoverIntent.js.

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

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

Вот код на языке JavaScript от авторов задачи, определяющий координаты подсказки (этот код приведен в тексте постановки задачи):
tooltip.style.left = elem.getBoundingClientRect().left + 'px';
tooltip.style.top = elem.getBoundingClientRect().bottom + 5 + 'px';

Но так как координаты подсказки определяются (авторами задачи в стиле подсказки) относительно левого верхнего угла HTML-страницы, а не левого верхнего угла области просмотра браузера, то логичнее было бы написать этот код следующим образом (красным цветом я отметил изменения):
tooltip.style.left = elem.getBoundingClientRect().left + pageXOffset + 'px';
tooltip.style.top = elem.getBoundingClientRect().bottom + pageYOffset + 5 + 'px';
Подробнее про это рассказано в подразделе 1.11 «Координаты» второй части обсуждаемого учебника.

В-третьих, фраза «передвигает мышь на него, но не через него» не имеет смысла. Если пользователь передвинул мышь на HTML-элемент, то после этого обязательно последует движение через этот HTML-элемент. И, наоборот, если пользователь двигает курсор мыши через HTML-элемент, то, значит, сначала он передвинул мышь на этот HTML-элемент. Подробнее см. мой пост с экспериментами.

Я переписал текст постановки задачи с учетом этих трех замечаний. У меня получилось следующее (всё еще неидеально, но лучше, чем было):

Допишите заданный в файле hoverIntent.js класс HoverIntent, объект которого показывает подсказку возле элемента.

Если пользователь подвинул курсор на элементе и остановился — показывать подсказку. А если он просто быстро провёл курсором по элементу, то не надо ничего показывать. Кому понравится лишнее мелькание?


* * *

Зачем нужен в данном случае класс HoverIntent и что он из себя представляет? И, действительно, задачу можно выполнить без этого класса, написав код, состоящий только из инструкций, переменных и функций, без создания объектов своего собственного класса (стандартные встроенные объекты, конечно, будут использоваться и в этом случае).

Фраза «hover intent» переводится на русский язык как «намерение навести» (имеется в виду намерение пользователя навести курсор мыши на HTML-элемент). Подсказка здесь и названа «умной», потому что скрипт анализирует поведение пользователя и на основе этого анализа определяет, желает ли пользователь получить подсказку, или он просто случайно провел мышью над HTML-элементом.

Как я понимаю, здесь этот класс введен для того, чтобы потихоньку обучать ученика приемам объектно-ориентированного программирования (ООП).

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

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

* * *

В тексте постановки задачи не уточняется, что конкретно в цифрах означает наречие «быстро» в фразе «он просто быстро провёл курсором по элементу» (гусары, молчать!). Но точные численные характеристики есть в коде класса HoverIntent. Они заданы в его свойствах sensitivity (по-русски «чувствительность») и interval.

Скорость передвижения курсора мыши по HTML-элементу следует замерять каждые 100 миллисекунд (это значение задано по умолчанию в свойстве interval, имеется в виду интервал между замерами скорости).

После замера скорости движения курсора мыши эту скорость следует сравнить с значением 0,1 пикселей в миллисекунду (это значение задано по умолчанию в свойстве sensitivity, имеется в виду чувствительность умной подсказки к быстроте передвижения курсора мыши). Если замеренная скорость движения курсора мыши меньше или равна 0,1 пикселей в миллисекунду, то следует показать подсказку. В противном случае считаем, что пользователь не собирался инициировать появление подсказки, а провел курсором мыши над HTML-элементом случайно.

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