События маркеров

Маркеры в MapLibre GL JS представляют собой DOM-элементы, размещённые поверх WebGL-карты, и их событийная модель принципиально отличается от событий самой карты. Основная особенность заключается в том, что маркеры не являются частью WebGL-слоя, поэтому все взаимодействия с ними обрабатываются через стандартную систему DOM-событий.


Архитектура событий маркера

Каждый маркер создаётся как обёртка над HTML-элементом:

const marker = new maplibregl.Marker()
  .setLngLat([30.5, 50.5])
  .addTo(map);

Внутри маркера находится DOM-узел, доступный через:

marker.getElement();

Именно этот элемент становится точкой подключения всех событий взаимодействия.


Базовые DOM-события

Так как маркер — это HTML-элемент, применяются стандартные события браузера:

  • click
  • dblclick
  • mouseenter
  • mouseleave
  • mousedown
  • mouseup
  • contextmenu

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

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 критично при наложении интерактивных слоёв и маркеров, чтобы избежать двойной обработки клика.


Перетаскиваемые маркеры и события drag

Маркер может быть сделан перетаскиваемым:

const marker = new maplibregl.Marker({ draggable: true })
  .setLngLat([30.5, 50.5])
  .addTo(map);

В этом режиме доступны специальные события:

  • dragstart
  • drag
  • dragend

Подключение осуществляется через сам объект маркера:

marker.on('dragstart', () => {
  console.log('начало перетаскивания');
});

marker.on('drag', () => {
  console.log(marker.getLngLat());
});

marker.on('dragend', () => {
  console.log('завершение перетаскивания');
});

Эти события уже не являются DOM-событиями и обрабатываются внутренней системой MapLibre.


Комбинирование DOM-событий и событий API

В реальных сценариях часто используется гибридная модель:

  • DOM-события — для кликов, hover, кастомного UI
  • API-события — для drag и состояния маркера

Пример объединения логики:

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-маркеры и расширенные события

При использовании кастомного 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-элемент становится узким местом.

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

  • делегирование событий через контейнер
  • минимизация обработчиков
  • использование CSS вместо JS для hover-эффектов

Пример делегирования:

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());
});

Такая модель позволяет строить реактивные интерфейсы на основе событийной системы маркеров.