Mouse события

Система обработки событий мыши в MapLibre GL JS основана на событийной модели браузера и внутреннем механизме диспетчеризации, который связывает DOM-события canvas с географическими объектами карты. Все взаимодействия пользователя с картой транслируются в высокоуровневые события, содержащие координаты, экранные пиксели и информацию о слоях, попавших под курсор.

Внутри MapLibre GL JS мышиные события формируются поверх HTMLCanvasElement, на котором рендерится карта WebGL. Браузер генерирует стандартные DOM-события (mousedown, mouseup, click, mousemove, contextmenu), которые библиотека перехватывает и преобразует в геопространственные события.

Каждое событие в API карты содержит:

  • lngLat — географические координаты точки взаимодействия
  • point — пиксельные координаты относительно контейнера карты
  • originalEvent — исходное DOM-событие браузера
  • features — список объектов карты, попавших под курсор (если применимо)

Такая структура позволяет работать как с низкоуровневыми пользовательскими действиями, так и с объектами слоёв (layers), не обращаясь напрямую к WebGL-сцене.

Подписка на события мыши

Основной механизм взаимодействия — метод map.on. Он позволяет подписываться как на глобальные события карты, так и на события конкретных слоёв.

Пример привязки события:

map.on('click', (e) => {
  console.log(e.lngLat);
});

Поддерживается два основных режима:

  • Глобальные события карты — срабатывают при любом взаимодействии
  • События слоя — срабатывают только при попадании курсора на конкретный слой

Пример событий слоя:

map.on('click', 'cities-layer', (e) => {
  console.log(e.features);
});

В этом случае обработчик получает только те геометрические объекты, которые принадлежат слою cities-layer.

Событие click

Событие click формируется при быстром нажатии и отпускании кнопки мыши без существенного перемещения курсора. Оно является наиболее часто используемым в интерактивных картах.

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

  • Срабатывает после mousedown и mouseup
  • Игнорирует небольшие движения курсора
  • Может возвращать несколько features при перекрытии слоёв

Пример обработки:

map.on('click', (e) => {
  const { lngLat, point, features } = e;

  if (features && features.length) {
    console.log(features[0].properties);
  }
});

При работе с векторными слоями важно учитывать, что порядок features зависит от порядка слоёв и их z-index.

События mousedown и mouseup

События mousedown и mouseup отражают момент нажатия и отпускания кнопки мыши соответственно.

Используются для:

  • реализации кастомного drag-интерфейса
  • измерительных инструментов
  • построения выделений

Пример:

map.on('mousedown', (e) => {
  console.log('Нажатие:', e.point);
});

map.on('mouseup', (e) => {
  console.log('Отпускание:', e.point);
});

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

Событие mousemove

mousemove — одно из наиболее частых событий, генерируемых при перемещении курсора над картой. Оно используется для:

  • отображения координат в реальном времени
  • подсветки объектов под курсором
  • динамического обновления UI

Пример:

map.on('mousemove', (e) => {
  const { lngLat } = e;
  console.log(lngLat.lng, lngLat.lat);
});

При активном использовании этого события важно учитывать производительность. Часто применяется троттлинг или requestAnimationFrame, чтобы избежать перегрузки основного потока.

События mouseenter и mouseleave для слоёв

MapLibre GL JS поддерживает специальные события для слоёв:

  • mouseenter
  • mouseleave

Они работают аналогично DOM-событиям и срабатывают при входе и выходе курсора из области объектов слоя.

Пример:

map.on('mouseenter', 'parks-layer', () => {
  map.getCanvas().style.cursor = 'pointer';
});

map.on('mouseleave', 'parks-layer', () => {
  map.getCanvas().style.cursor = '';
});

Эти события часто используются для улучшения UX, например изменения курсора или отображения подсказок.

Событие contextmenu

Событие contextmenu возникает при вызове контекстного меню (обычно правой кнопкой мыши). Оно полезно для реализации пользовательских меню действий на карте.

Пример:

map.on('contextmenu', (e) => {
  console.log('Контекстное меню:', e.lngLat);
});

Часто используется совместно с preventDefault для подавления стандартного поведения браузера.

map.on('contextmenu', (e) => {
  e.preventDefault();
});

Работа с features и queryRenderedFeatures

Одной из ключевых возможностей мышиных событий является доступ к объектам карты через features. Однако при необходимости можно выполнять более гибкие запросы:

map.on('click', (e) => {
  const features = map.queryRenderedFeatures(e.point, {
    layers: ['cities-layer']
  });

  console.log(features);
});

Метод queryRenderedFeatures позволяет:

  • фильтровать объекты по слоям
  • получать данные без привязки к событию
  • анализировать сцену в произвольной точке

Это особенно важно при сложных визуализациях с перекрывающимися слоями.

Координаты point и lngLat

Каждое событие содержит два типа координат:

  • point — координаты в пикселях относительно контейнера карты
  • lngLat — географические координаты (долгота и широта)

Различие принципиально:

  • point используется для UI-логики (оверлеи, позиционирование)
  • lngLat используется для географических операций

Пример преобразования:

map.on('click', (e) => {
  const screen = e.point;
  const geo = e.lngLat;

  console.log(screen, geo);
});

Поведение при масштабировании и вращении карты

Мышиные события учитывают трансформации карты:

  • масштабирование (zoom)
  • вращение (bearing)
  • наклон (pitch)

Это означает, что point всегда соответствует текущему состоянию canvas, а lngLat пересчитывается в соответствии с матрицей трансформации камеры.

При резких изменениях камеры события могут временно агрегироваться для оптимизации производительности.

Конфликты событий и их приоритет

При одновременной работе нескольких слоёв события обрабатываются по следующему принципу:

  1. Верхние слои имеют приоритет
  2. Первый найденный feature может прерывать дальнейшую обработку (если не включён passive)
  3. Порядок слоёв в стиле влияет на порядок событий

Это важно при проектировании интерактивных карт, где несколько объектов перекрывают друг друга.

Использование originalEvent

Свойство originalEvent позволяет получить доступ к исходному DOM-событию:

map.on('click', (e) => {
  console.log(e.originalEvent.button);
});

Это используется для:

  • определения кнопки мыши
  • доступа к модификаторам (Shift, Ctrl)
  • интеграции с кастомными обработчиками DOM

Пример проверки модификаторов:

map.on('click', (e) => {
  if (e.originalEvent.shiftKey) {
    console.log('Shift + клик');
  }
});

Drag-поведение через мышиные события

Хотя MapLibre GL JS не предоставляет отдельного drag API для пользовательских объектов, оно легко реализуется через комбинацию событий:

  • mousedown
  • mousemove
  • mouseup

Пример логики:

let isDragging = false;

map.on('mousedown', (e) => {
  isDragging = true;
});

map.on('mousemove', (e) => {
  if (!isDragging) return;
  console.log('Перетаскивание:', e.lngLat);
});

map.on('mouseup', () => {
  isDragging = false;
});

Такая модель используется для рисования, редактирования геометрии и интерактивных инструментов.

Производительность при интенсивных mousemove событиях

Событие mousemove может генерироваться десятки раз в секунду. При сложной логике это приводит к деградации производительности.

Типичные подходы оптимизации:

  • ограничение частоты обработки (throttling)
  • использование requestAnimationFrame
  • минимизация работы внутри обработчика
  • кэширование queryRenderedFeatures

Пример с requestAnimationFrame:

let scheduled = false;

map.on('mousemove', (e) => {
  if (scheduled) return;

  scheduled = true;

  requestAnimationFrame(() => {
    console.log(e.lngLat);
    scheduled = false;
  });
});

Взаимодействие мышиных событий с слоями и стилями

Мышиные события тесно связаны со стилями карты. Каждый слой может иметь:

  • фильтры
  • видимость
  • порядок отрисовки

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