Маркеры в MapLibre GL JS представляют собой DOM-элементы, размещённые поверх WebGL-карты, и их событийная модель принципиально отличается от событий самой карты. Основная особенность заключается в том, что маркеры не являются частью WebGL-слоя, поэтому все взаимодействия с ними обрабатываются через стандартную систему DOM-событий.
Каждый маркер создаётся как обёртка над HTML-элементом:
const marker = new maplibregl.Marker()
.setLngLat([30.5, 50.5])
.addTo(map);
Внутри маркера находится DOM-узел, доступный через:
marker.getElement();
Именно этот элемент становится точкой подключения всех событий взаимодействия.
Так как маркер — это HTML-элемент, применяются стандартные события браузера:
clickdblclickmouseentermouseleavemousedownmouseupcontextmenuПодключение выполняется напрямую:
const el = marker.getElement();
el.addEventListener('click', (e) => {
console.log('Клик по маркеру');
});
Ключевой момент заключается в том, что события не проходят через API карты, поэтому логика обработки полностью контролируется через DOM.
События карты:
map.on('click', (e) => {
console.log(e.lngLat);
});
События маркера:
marker.getElement().addEventListener('click', () => {
console.log('маркер');
});
При клике на маркер происходит всплытие события (event bubbling), поэтому обработчики карты могут также сработать, если не остановлено распространение:
marker.getElement().addEventListener('click', (e) => {
e.stopPropagation();
});
Использование stopPropagation критично при наложении
интерактивных слоёв и маркеров, чтобы избежать двойной обработки
клика.
Маркер может быть сделан перетаскиваемым:
const marker = new maplibregl.Marker({ draggable: true })
.setLngLat([30.5, 50.5])
.addTo(map);
В этом режиме доступны специальные события:
dragstartdragdragendПодключение осуществляется через сам объект маркера:
marker.on('dragstart', () => {
console.log('начало перетаскивания');
});
marker.on('drag', () => {
console.log(marker.getLngLat());
});
marker.on('dragend', () => {
console.log('завершение перетаскивания');
});
Эти события уже не являются DOM-событиями и обрабатываются внутренней системой MapLibre.
В реальных сценариях часто используется гибридная модель:
Пример объединения логики:
const el = marker.getElement();
el.addEventListener('mouseenter', () => {
el.classList.add('highlight');
});
el.addEventListener('mouseleave', () => {
el.classList.remove('highlight');
});
marker.on('dragend', () => {
const coords = marker.getLngLat();
console.log(coords);
});
Такое разделение снижает связность кода и упрощает управление состоянием.
При использовании кастомного HTML:
const el = document.createElement('div');
el.className = 'custom-marker';
el.innerHTML = '<div class="pin"></div>';
const marker = new maplibregl.Marker({ element: el })
.setLngLat([30.5, 50.5])
.addTo(map);
Появляется возможность навешивать произвольные события:
el.addEventListener('click', () => {
console.log('кастомный маркер');
});
el.addEventListener('mouseover', () => {
el.style.transform = 'scale(1.1)';
});
Такой подход позволяет реализовать сложные UI-компоненты, включая интерактивные карточки, мини-виджеты и индикаторы состояния.
Поскольку маркеры находятся поверх карты, часто возникает конфликт между:
Типичный механизм управления:
el.addEventListener('click', (e) => {
e.stopPropagation();
});
Без этого карта будет одновременно регистрировать событие клика.
Дополнительно можно использовать проверку состояния:
map.on('click', (e) => {
if (e.originalEvent.target.closest('.custom-marker')) return;
console.log('клик по карте');
});
При изменении положения маркера:
marker.setLngLat([31, 51]);
DOM-события не генерируются автоматически. Если требуется реакция на изменение позиции, используется внешний триггер:
function updateMarkerPosition(marker, coords) {
marker.setLngLat(coords);
marker.getElement().dispatchEvent(
new CustomEvent('positionchange', { detail: coords })
);
}
И обработка:
marker.getElement().addEventListener('positionchange', (e) => {
console.log(e.detail);
});
При работе с десятками тысяч маркеров прямое навешивание событий на каждый DOM-элемент становится узким местом.
Оптимизационные подходы:
Пример делегирования:
const container = document.querySelector('.map-container');
container.addEventListener('click', (e) => {
const marker = e.target.closest('.custom-marker');
if (!marker) return;
console.log('клик по маркеру через делегирование');
});
При использовании кластеров поведение событий меняется: клик может обрабатываться на уровне кластера, а не отдельного маркера.
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point);
console.log(features);
});
В таких сценариях маркеры внутри кластера не получают DOM-события напрямую, так как не являются отдельными элементами на карте до раскрытия кластера.
Удаление маркера автоматически приводит к удалению его DOM-узла:
marker.remove();
После этого все навешанные DOM-события становятся недоступными через стандартный механизм, но важно избегать утечек, если ссылки на обработчики сохраняются в замыканиях.
Для очистки логики часто используется явное обнуление:
const el = marker.getElement();
el.oncl ick = null;
el.onmouseo ver = null;
При наложении слоёв MapLibre поверх маркеров возможна ситуация, когда WebGL-слой перехватывает события мыши.
Регулировка выполняется через:
map.getCanvas().style.pointerEvents = 'auto';
Или через управление interactive слоями:
map.addLayer({
id: 'points',
type: 'circle',
source: 'points',
paint: {
'circle-radius': 6
}
});
Слои с interactive поведением могут блокировать
DOM-события маркеров при перекрытии.
Маркер часто выступает связующим элементом между картой и внешним интерфейсом. События используются для синхронизации:
marker.getElement().addEventListener('click', () => {
sidebar.open();
sidebar.setData(marker.getLngLat());
});
И обратная синхронизация:
map.on('move', () => {
sidebar.updateViewport(map.getCenter());
});
Такая модель позволяет строить реактивные интерфейсы на основе событийной системы маркеров.