В Mapbox GL JS маркеры представляют собой интерактивные DOM-элементы, привязанные к координатам на карте, и именно через них реализуется значительная часть пользовательского взаимодействия: клики, перетаскивание, наведение, открытие всплывающих окон и интеграция с внешними состояниями приложения. Обработка событий маркера строится на сочетании стандартной DOM-модели событий и внутренней логики карты, что требует понимания как поведения самого маркера, так и особенностей событийного потока внутри карты.
Маркер в Mapbox GL JS не является отдельным графическим примитивом
WebGL. Он создаётся как HTML-элемент, размещаемый поверх карты в
отдельном слое DOM. Это определяет ключевую особенность: события маркера
обрабатываются через стандартные браузерные механизмы
addEventListener, а не через WebGL-подсистему карты.
Каждый маркер содержит:
div)Такой подход делает маркеры гибкими, но накладывает ограничения: обработка событий зависит от правил всплытия DOM-событий и взаимодействия с контейнером карты.
Наиболее распространённый сценарий — обработка клика по маркеру. Поскольку маркер является DOM-элементом, стандартный обработчик добавляется напрямую:
const markerElement = document.createElement('div');
markerElement.className = 'marker';
markerElement.addEventListener('click', (e) => {
console.log('Клик по маркеру');
});
Клик обрабатывается до того, как событие может быть интерпретировано
картой. Это важно: если не контролировать всплытие, клик по маркеру
может одновременно вызвать обработчики карты, например
map.on('click').
Для предотвращения нежелательного распространения события используется:
markerElement.addEventListener('click', (e) => {
e.stopPropagation();
});
Это предотвращает передачу события в слой карты и позволяет разделить поведение маркера и карты.
События маркера часто должны взаимодействовать с событиями карты. Например, открытие всплывающего окна при клике требует синхронизации с состоянием карты:
markerElement.addEventListener('click', () => {
popup.setLngLat([lng, lat]).addTo(map);
});
Однако при сложных интерфейсах важно учитывать конкуренцию событий: карта может реагировать на тот же клик (панорамирование, зум, обработка feature query). Поэтому часто применяется стратегия фильтрации:
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
if (features.length === 0) {
popup.remove();
}
});
Таким образом, маркерные события не изолированы, а являются частью общего событийного контекста карты.
Одной из ключевых возможностей является поддержка drag-событий. Для этого используется встроенная функциональность маркера:
const marker = new mapboxgl.Marker({
draggable: true
});
После активации перетаскивания становятся доступны события:
dragstartdragdragendСрабатывает при начале взаимодействия. Используется для фиксации начального состояния:
marker.on('dragstart', () => {
console.log('Начало перетаскивания');
});
Срабатывает непрерывно во время перемещения. Часто применяется для обновления координат UI:
marker.on('drag', () => {
const lngLat = marker.getLngLat();
console.log(lngLat);
});
Важно учитывать производительность: событие может вызываться десятки раз в секунду, поэтому тяжёлые вычисления внутри обработчика недопустимы.
Фиксирует финальную позицию:
marker.on('dragend', () => {
const position = marker.getLngLat();
savePosition(position);
});
Этот этап обычно используется для синхронизации с сервером или обновления состояния приложения.
Хотя Mapbox GL JS не предоставляет специализированных событий hover для маркеров, они легко реализуются через DOM-события:
markerElement.addEventListener('mouseenter', () => {
markerElement.classList.add('hover');
});
markerElement.addEventListener('mouseleave', () => {
markerElement.classList.remove('hover');
});
Такие события полезны для визуальной индикации активного маркера, изменения масштаба или отображения вспомогательных элементов.
Важно учитывать, что на сенсорных устройствах mouseenter
и mouseleave могут не срабатывать, поэтому требуется
альтернативная логика через touchstart.
На мобильных устройствах основным источником взаимодействия становятся события:
touchstarttouchmovetouchendПример обработки:
markerElement.addEventListener('touchstart', (e) => {
console.log('Касание маркера');
});
Особенность заключается в том, что touch-события часто конфликтуют с жестами карты (панорамирование и масштабирование). Поэтому требуется явное управление:
markerElement.addEventListener('touchstart', (e) => {
e.stopPropagation();
});
Без этого карта может перехватывать жест, что делает взаимодействие с маркером нестабильным.
При большом количестве маркеров прямое навешивание обработчиков на каждый элемент приводит к росту нагрузки на память и снижению производительности. В таких случаях применяется делегирование событий.
Подход основан на общем контейнере:
map.getCanvasContainer().addEventListener('click', (e) => {
const target = e.target;
if (target.classList.contains('marker')) {
console.log('Клик по маркеру через делегирование');
}
});
Это снижает количество обработчиков и упрощает управление динамическими маркерами.
Mapbox GL JS позволяет создавать полностью кастомные маркеры:
const el = document.createElement('div');
el.innerHTML = '<div class="custom-marker"></div>';
const marker = new mapboxgl.Marker(el)
.setLngLat([lng, lat])
.addTo(map);
В этом случае обработка событий полностью контролируется разработчиком. Это даёт гибкость, но требует ручного управления:
Особенно важно правильно учитывать pointer-events в CSS,
иначе события могут не доходить до элемента:
.custom-marker {
pointer-events: auto;
}
События маркеров часто конфликтуют с событиями карты:
Для управления приоритетами используется комбинация:
stopPropagation()map.dragPan.disable()Пример:
marker.on('dragstart', () => {
map.dragPan.disable();
});
marker.on('dragend', () => {
map.dragPan.enable();
});
Это позволяет гарантировать, что перетаскивание маркера не будет конфликтовать с перемещением карты.
Обработка событий маркеров часто используется для синхронизации UI и внешнего состояния:
Пример синхронизации:
marker.on('dragend', () => {
const { lng, lat } = marker.getLngLat();
updateStore({ lng, lat });
});
Такой подход превращает маркер из визуального элемента в источник событий бизнес-логики.
При динамическом создании маркеров важно корректно удалять обработчики, иначе возникают утечки памяти.
function createMarker() {
const el = document.createElement('div');
function onClick() {
console.log('click');
}
el.addEventListener('click', onClick);
return {
el,
destroy() {
el.removeEventListener('click', onClick);
}
};
}
При удалении маркера из карты необходимо вызывать очистку:
marker.remove();
И дополнительно очищать связанные ресурсы.
В сложных приложениях маркеры становятся частью событийной системы:
Например, при обновлении данных в реальном времени:
socket.on('update', (data) => {
marker.setLngLat(data.coords);
});
События при этом могут генерироваться не пользователем, а системой, что требует разделения типов событий: пользовательских и программных.
В практических архитектурах выделяются несколько устойчивых моделей:
addEventListenermap.onКаждый из этих подходов определяет, где именно обрабатываются события маркеров — на уровне DOM, карты или приложения.