HTML маркеры

HTML-маркеры в Mapbox GL JS представляют собой механизм отображения на карте произвольных DOM-элементов вместо стандартных векторных или растровых иконок. Такой подход позволяет создавать полностью кастомные интерактивные объекты: сложные бейджи, карточки, индикаторы состояния, мини-интерфейсы и динамические элементы, которые синхронизируются с координатами карты.

В отличие от стандартных SymbolLayer или CircleLayer, HTML-маркер основан на обычном DOM-элементе, который привязывается к координатам карты через API Marker.

Создание маркера начинается с формирования HTML-элемента:

const el = document.createElement('div');
el.className = 'custom-marker';
el.innerHTML = '<div class="dot"></div><span>Точка</span>';

Далее элемент передаётся в конструктор:

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

Ключевая особенность — полный контроль над содержимым маркера через DOM.

Архитектура HTML-маркеров

HTML-маркеры не являются частью WebGL-рендеринга карты. Они существуют поверх canvas-слоя и позиционируются через трансформации CSS (transform: translate(...)), которые пересчитываются при каждом изменении состояния карты: панорамировании, зуме или вращении.

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

  • производительность зависит от количества DOM-элементов
  • каждый маркер — отдельный узел дерева документа
  • взаимодействие реализуется через стандартные DOM-события
  • стилизация выполняется через CSS

Создание кастомного DOM-элемента

На практике HTML-маркер почти всегда включает структуру:

const el = document.createElement('div');
el.className = 'marker';

const icon = document.createElement('div');
icon.className = 'marker__icon';

const label = document.createElement('div');
label.className = 'marker__label';
label.textContent = 'Объект';

el.appendChild(icon);
el.appendChild(label);

CSS определяет внешний вид:

.marker {
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
}

.marker__icon {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #ff4d4f;
}

.marker__label {
  font-size: 12px;
  color: #333;
  margin-top: 4px;
}

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

Позиционирование и якорение

HTML-маркеры поддерживают настройку точки привязки через anchor:

const marker = new mapboxgl.Marker(el, {
  anchor: 'bottom'
})
  .setLngLat([30.5, 50.5])
  .addTo(map);

Доступные значения:

  • center
  • top
  • bottom
  • left
  • right
  • комбинации (top-left, bottom-right)

Якорь определяет, какая точка DOM-элемента совпадает с координатой на карте.

Смещение маркера

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

const marker = new mapboxgl.Marker(el, {
  offset: [0, -10]
})
  .setLngLat([30.5, 50.5])
  .addTo(map);

Offset особенно важен при нестандартных формах маркеров, где центр геометрии не совпадает с визуальным центром.

Обработка событий

HTML-маркеры поддерживают нативные события DOM:

el.addEventListener('click', () => {
  console.log('Маркер нажат');
});

Также можно комбинировать с всплывающими окнами:

const popup = new mapboxgl.Popup({ offset: 25 })
  .setText('Информация об объекте');

const marker = new mapboxgl.Marker(el)
  .setLngLat([30.5, 50.5])
  .setPopup(popup)
  .addTo(map);

Popup привязывается к маркеру и автоматически синхронизируется с его положением.

Динамическое обновление маркеров

Положение маркера может изменяться в реальном времени:

marker.setLngLat([31.0, 51.0]);

Также можно полностью заменить содержимое DOM:

el.innerHTML = '<div class="updated">Новый статус</div>';

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

Удаление и управление жизненным циклом

Удаление маркера из карты:

marker.remove();

При удалении DOM-элемент также исключается из слоя карты, но при этом может сохраняться в памяти, если на него остаются ссылки.

Рекомендуется явно очищать обработчики событий:

el.removeEventListener('click', handler);

Производительность и масштабирование

HTML-маркеры являются одним из наиболее ресурсоёмких способов отображения объектов на карте.

Основные ограничения:

  • большое количество DOM-элементов снижает FPS
  • сложные стили (тени, blur, анимации) увеличивают нагрузку
  • частые обновления координат вызывают перерасчёт layout

Практические оптимизации:

  • ограничение количества маркеров
  • использование кластеризации через GeoJSONSource
  • минимизация DOM-структуры
  • отказ от тяжёлых CSS-эффектов
  • применение will-change: transform

Кластеризация и HTML-маркеры

При большом объёме данных HTML-маркеры часто комбинируются с кластеризацией:

map.addSource('points', {
  type: 'geojson',
  data: geojson,
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 50
});

Кластеры отображаются как отдельные HTML-маркеры, содержащие число объектов:

const el = document.createElement('div');
el.className = 'cluster-marker';
el.textContent = count;

Такой подход уменьшает нагрузку и повышает читаемость карты.

Перетаскиваемые маркеры

HTML-маркеры могут быть интерактивными:

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

Обработка изменения позиции:

marker.on('dragend', () => {
  const lngLat = marker.getLngLat();
  console.log(lngLat);
});

Это часто используется в интерфейсах выбора точки на карте.

Слои поверх карты и z-index

HTML-маркеры всегда находятся поверх canvas, но порядок их наложения регулируется CSS:

.marker {
  position: absolute;
  z-index: 10;
}

Для сложных интерфейсов применяются уровни:

  • базовые маркеры
  • активные маркеры
  • всплывающие элементы

Адаптация под масштаб карты

В отличие от WebGL-слоёв, HTML-маркеры не автоматически меняют визуальный масштаб.

Компенсация реализуется вручную:

map.on('zoom', () => {
  const zoom = map.getZoom();
  el.style.transform = `scale(${zoom / 10})`;
});

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

Доступность и взаимодействие

HTML-маркеры наследуют поведение DOM и могут быть адаптированы для доступности:

el.setAttribute('role', 'button');
el.setAttribute('tabindex', '0');

Обработка клавиатуры:

el.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') {
    console.log('Активирован маркер');
  }
});

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

Гибридные подходы

В реальных приложениях HTML-маркеры часто используются вместе с WebGL-слоями:

  • WebGL для массовых данных
  • HTML для интерактивных точек
  • Canvas overlay для кастомной графики

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

Управление множеством маркеров

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

const markers = [];

data.forEach(point => {
  const marker = new mapboxgl.Marker(el.cloneNode(true))
    .setLngLat(point.coords)
    .addTo(map);

  markers.push(marker);
});

Для массовых операций:

markers.forEach(m => m.remove());
markers.length = 0;

Типовые ошибки реализации

  • создание тяжёлой DOM-структуры для каждого маркера
  • отсутствие переиспользования элементов
  • частые вызовы setLngLat в цикле анимации
  • игнорирование кластеризации при больших наборах данных
  • использование CSS-анимаций без ограничения количества элементов

Поведение при трансформациях карты

HTML-маркеры автоматически синхронизируются с:

  • pan (перемещение)
  • zoom (масштабирование)
  • rotate (вращение)

При этом пересчёт позиции происходит через внутренний layout engine, что делает важным контроль количества элементов и их сложности.