Click, dblclick, contextmenu

Mapbox GL JS предоставляет событийную модель, основанную на подписке через map.on, где взаимодействие пользователя с картой транслируется в высокоуровневые события. Среди наиболее часто используемых — click, dblclick, contextmenu, формирующие основу интерактивных веб-карт.

Каждое событие в Mapbox GL JS привязывается к объекту карты и может быть глобальным либо контекстным (привязанным к слою или источнику данных):

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

Объект события содержит ключевые данные:

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

Для работы с интерактивными слоями обычно используется фильтрация по слоям:

map.on('click', 'cities-layer', (e) => {
  const feature = e.features[0];
  console.log(feature.properties);
});

Такой подход активирует обработчик только при клике по конкретному слою.

Событие click: базовое взаимодействие

click является фундаментом пользовательской интерактивности. Он используется для:

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

Часто применяется совместно с queryRenderedFeatures, когда требуется контроль без привязки к событию слоя:

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

  if (!features.length) return;

  const feature = features[0];
  new mapboxgl.Popup()
    .setLngLat(e.lngLat)
    .setHTML(`<strong>${feature.properties.name}</strong>`)
    .addTo(map);
});

Метод queryRenderedFeatures позволяет извлекать объекты, которые визуально присутствуют на экране, что делает обработку точной с точки зрения отображаемого состояния карты.

dblclick: двойной клик и управление масштабированием

Событие dblclick по умолчанию связано с поведением масштабирования карты. При двойном клике Mapbox GL JS увеличивает масштаб.

Для полного контроля поведения используется подавление стандартного действия:

map.doubleClickZoom.disable();

После отключения встроенного поведения появляется возможность реализовать собственную логику:

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

  if (features.length) {
    console.log('Double click feature:', features[0]);
  }
});

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

contextmenu: контекстное меню и правый клик

Событие contextmenu возникает при правом клике мышью. По умолчанию браузер отображает системное контекстное меню, которое обычно требуется подавить:

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

Далее Mapbox GL JS позволяет обработать событие как часть карты:

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

  if (!features.length) return;

  const feature = features[0];

  new mapboxgl.Popup()
    .setLngLat(e.lngLat)
    .setHTML(`
      <div>
        <div>Объект: ${feature.properties.name}</div>
        <div>Тип: ${feature.properties.type}</div>
      </div>
    `)
    .addTo(map);
});

Контекстное меню часто используется для:

  • действий над объектом (удаление, редактирование)
  • отображения расширенной информации
  • вызова кастомных UI-компонентов

Комбинация событий и приоритеты взаимодействия

В интерактивных приложениях события click, dblclick и contextmenu часто пересекаются по времени и области действия. Для корректного поведения используется управление приоритетами:

map.on('click', handleClick);
map.on('dblclick', handleDoubleClick);
map.on('contextmenu', handleContextMenu);

Дополнительно применяется контроль распространения событий:

map.on('click', (e) => {
  if (e.originalEvent.shiftKey) {
    e.originalEvent.stopPropagation();
  }
});

Работа с event.originalEvent

Поле originalEvent содержит нативное событие DOM, что позволяет использовать дополнительные модификаторы:

  • shiftKey
  • altKey
  • ctrlKey / metaKey
  • координаты мыши
  • кнопка мыши (button)

Пример различения действий:

map.on('click', (e) => {
  if (e.originalEvent.altKey) {
    console.log('Alt + click режим');
    return;
  }

  console.log('Обычный клик');
});

Это позволяет расширять поведение карты без создания дополнительных UI-элементов.

Отличие событий слоя и глобальных событий

Mapbox GL JS поддерживает два уровня подписки:

Слой-ориентированные события:

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

Глобальные события:

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

События слоя автоматически включают features, тогда как глобальные требуют явного запроса через queryRenderedFeatures.

Поведение на мобильных устройствах

На сенсорных экранах:

  • click заменяет tap
  • contextmenu часто эмулируется долгим нажатием
  • dblclick может конфликтовать с системным зумом браузера

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

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

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

Частые вызовы queryRenderedFeatures в mousemove или touchmove могут создавать нагрузку. Для click, dblclick, contextmenu это обычно не критично, но при расширении логики следует учитывать:

  • минимизацию поиска слоёв
  • ограничение количества вычислений внутри обработчиков
  • кеширование результатов при сложной геометрии

Оптимизированный вариант:

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

  if (!features.length) return;

  requestAnimationFrame(() => {
    updateUI(features[0]);
  });
});

Управление конфликтами взаимодействий

При наличии нескольких обработчиков часто требуется предотвращение одновременного срабатывания логики:

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

Также применяется флаговое управление состоянием:

let isEditing = false;

map.on('click', (e) => {
  if (isEditing) return;
  handleSelection(e);
});

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

Сочетание с popup и UI-слоем

События клика часто связываются с отображением всплывающих окон:

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

  if (!features.length) return;

  const feature = features[0];

  new mapboxgl.Popup({ offset: 10 })
    .setLngLat(e.lngLat)
    .setHTML(`<div>${feature.properties.name}</div>`)
    .addTo(map);
});

Контекстное меню может использоваться как расширение popup-системы, создавая альтернативную точку входа для действий над объектом.

Особенности поведения при масштабировании и вращении

При активной трансформации карты (zoom, rotate, pitch) координаты событий сохраняют точность в lngLat, однако экранные координаты point зависят от текущей матрицы отображения.

Это важно при:

  • привязке DOM-элементов к карте
  • анимации UI поверх карты
  • синхронизации кастомных слоёв
map.on('click', (e) => {
  const { lngLat, point } = e;
  console.log(lngLat, point);
});

Разделение логики географических и экранных координат позволяет корректно строить интерфейсы поверх WebGL-карты.