Управление курсором

В экосистеме HERE Technologies управление курсором в JavaScript API является частью более широкого слоя взаимодействия пользователя с картой, где DOM-события, слой рендеринга WebGL/Canvas и поведенческие модели карты работают совместно. Курсор выступает не просто визуальным индикатором, а сигналом состояния интерфейса: наведение на интерактивный объект, начало перетаскивания, режим выделения, блокировка взаимодействий или работа с пользовательскими слоями.

Карта в HERE Maps JS API рендерится внутри DOM-элемента, который инкапсулирует WebGL/Canvas поверхность. Управление курсором осуществляется через стиль этого контейнера или через события взаимодействия:

  • map.getElement() — основной DOM-контейнер карты
  • map.getViewPort().getElement() — слой viewport, часто используется для стилизации
  • события pointerdown, pointermove, pointerup
  • поведенческий слой H.mapevents.Behavior

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

Изменение курсора через DOM-контейнер

Наиболее прямой способ управления курсором — модификация CSS свойства cursor у контейнера карты:

const mapContainer = map.getElement();

mapContainer.style.cursor = 'default';

Поддерживаются стандартные значения CSS:

  • default
  • pointer
  • grab
  • grabbing
  • crosshair
  • move

Для более точного контроля часто используется переключение состояний:

map.addEventListener('pointerdown', () => {
  map.getElement().style.cursor = 'grabbing';
});

map.addEventListener('pointerup', () => {
  map.getElement().style.cursor = 'grab';
});

map.addEventListener('pointermove', () => {
  map.getElement().style.cursor = 'grab';
});

Такой подход особенно важен при реализации кастомного drag-поведения, когда стандартный Behavior отключён или расширен.

Интеграция с H.mapevents.Behavior

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

const beh * avior = new H.mapevents.Behavior(
  new H.mapevents.MapEvents(map)
);

В этом режиме:

  • при удержании кнопки мыши курсор переходит в grabbing
  • при наведении на интерактивные элементы может становиться pointer
  • при инерционном движении сохраняется состояние drag

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

Реакция курсора на объекты карты

Карта состоит из интерактивных слоёв (markers, polylines, polygons), каждый из которых может иметь события:

  • pointerenter
  • pointerleave
  • tap
  • pointerdown

Пример изменения курсора при наведении на маркер:

marker.addEventListener('pointerenter', () => {
  map.getElement().style.cursor = 'pointer';
});

marker.addEventListener('pointerleave', () => {
  map.getElement().style.cursor = 'default';
});

Этот механизм позволяет визуально отделять интерактивные объекты от фоновой карты.

Работа с H.map.Event

Низкоуровневая система событий H.map.Event позволяет перехватывать глобальные события карты:

map.addEventListener('pointermove', function (evt) {
  const target = evt.target;

  if (target instanceof H.map.Marker) {
    map.getElement().style.cursor = 'pointer';
  } else {
    map.getElement().style.cursor = 'default';
  }
});

Здесь важным является анализ evt.target, который указывает на объект сцены, находящийся под курсором.

Разделение состояний взаимодействия

В сложных интерфейсах курсор используется как индикатор режима:

Режим перемещения карты

map.getElement().style.cursor = 'grab';

При удержании:

map.getElement().style.cursor = 'grabbing';

Режим выбора объектов

map.getElement().style.cursor = 'crosshair';

Режим блокировки взаимодействия

map.getElement().style.cursor = 'not-allowed';
map.setInteractive(false);

Хотя метод setInteractive не является универсальным API, часто используется логическое отключение поведения через H.mapevents.Behavior.disable() или удаление обработчиков.

Кастомные курсоры

Курсор может быть заменён на изображение:

map.getElement().style.cursor = 'url(cursor.png) 12 12, auto';

Особенности:

  • hot-spot (координаты активной точки) критичен для точности
  • изображение должно быть оптимизировано (малый размер)
  • fallback обязателен (auto)

Кастомные курсоры часто применяются в:

  • режимах рисования маршрутов
  • инструментах измерения расстояния
  • редакторах геометрии

Управление курсором при работе с оверлеями

При использовании DOM-оверлеев поверх карты курсор может конфликтовать между слоями. В таких случаях важно контролировать pointer-events:

.overlay {
  pointer-events: none;
}

Или наоборот:

.overlay {
  pointer-events: auto;
}

Это влияет на то, какой слой получает события, а следовательно — какой стиль курсора применяется.

Синхронизация курсора с географическими координатами

Хотя курсор существует в экранных координатах, его положение можно преобразовать в географические:

map.addEventListener('pointermove', (evt) => {
  const coord = map.screenToGeo(
    evt.currentPointer.viewportX,
    evt.currentPointer.viewportY
  );

  console.log(coord.lat, coord.lng);
});

На основе этих данных можно динамически менять курсор в зависимости от зоны карты (например, разные регионы или полигоны).

Производительные аспекты

Частое изменение cursor через прямое обновление DOM может приводить к лишним reflow/repaint. Оптимизация включает:

  • минимизацию переключений состояния
  • хранение текущего состояния курсора
  • проверку перед установкой
function setCursor(cursor) {
  const el = map.getElement();
  if (el.style.cursor !== cursor) {
    el.style.cursor = cursor;
  }
}

Типовые паттерны управления курсором

Hover-паттерн

  • enter → pointer
  • leave → default

Drag-паттерн

  • mousedown → grabbing
  • mousemove → grabbing
  • mouseup → grab

Tool-mode паттерн

  • выбор инструмента → crosshair
  • работа инструмента → custom cursor
  • завершение → default

Ошибки и конфликтные состояния

На практике часто возникают ситуации, когда курсор “залипает”. Основные причины:

  • не обработан pointerup
  • конфликт нескольких слушателей событий
  • наличие overlay с pointer-events: auto
  • активный Behavior и кастомный handler одновременно

Решение обычно сводится к централизованному управлению состоянием курсора через единый контроллер, который синхронизирует события карты и UI-инструментов.