HTML-контент

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

Встраивание HTML через Popup

Popup — основной механизм отображения HTML-контента, привязанного к координате.

const popup = new mapboxgl.Popup({
  closeButton: true,
  closeOnClick: false
});

popup.setHTML(`
  <div class="popup-card">
    <h3>Объект на карте</h3>
    <p>Описание точки интереса</p>
  </div>
`);

popup.setLngLat([71.4304, 51.1284]).addTo(map);

Метод setHTML принимает строку HTML и вставляет её внутрь контейнера попапа. При этом содержимое не проходит автоматическую очистку, поэтому управление безопасностью ложится на разработчика.

Альтернативный метод — setDOMContent, позволяющий использовать уже созданный DOM-узел:

const container = document.createElement('div');
container.className = 'popup-card';

const title = document.createElement('h3');
title.textContent = 'Объект на карте';

const description = document.createElement('p');
description.textContent = 'Описание точки интереса';

container.appendChild(title);
container.appendChild(description);

popup.setDOMContent(container);

Использование setDOMContent предпочтительно при динамических интерфейсах, так как позволяет избегать повторного парсинга HTML и упрощает работу с событиями.

Безопасность HTML-контента

HTML-инъекции в popups — одна из частых проблем при работе с картами. Поскольку setHTML напрямую вставляет строку в DOM, любое неподготовленное содержимое может привести к XSS-уязвимостям.

Типовой подход к защите:

  • экранирование пользовательского ввода
  • отказ от прямого innerHTML в пользу DOM-методов
  • использование шаблонизаторов с автоматическим escaping
function escapeHTML(str) {
  return str.replace(/[&<>"']/g, function (m) {
    return ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#039;'
    })[m];
  });
}

popup.setHTML(`
  <div>
    <h3>${escapeHTML(userTitle)}</h3>
  </div>
`);

HTML-маркеры и кастомные DOM-элементы

Mapbox GL JS позволяет создавать маркеры на основе произвольных DOM-узлов. Это ключевой механизм для визуально сложных интерфейсов на карте.

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

el.innerHTML = `
  <div class="marker-content">
    <span class="marker-icon"></span>
  </div>
`;

new mapboxgl.Marker(el)
  .setLngLat([71.4304, 51.1284])
  .addTo(map);

HTML-маркер существует как полноценный DOM-элемент, который можно стилизовать через CSS:

.custom-marker {
  width: 30px;
  height: 30px;
  background: white;
  border-radius: 50%;
  box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}

.marker-icon {
  display: block;
  width: 100%;
  height: 100%;
  background: url(icon.svg) center/contain no-repeat;
}

Главная особенность HTML-маркеров — их производительность. При большом количестве точек (сотни и тысячи) DOM-узлы становятся тяжёлыми, поэтому такие маркеры применяются только для ограниченных наборов данных.

Управление событиями внутри HTML-элементов

DOM-маркеры и popups поддерживают стандартную модель событий браузера. Это позволяет строить интерактивные компоненты прямо внутри карты.

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

el.innerHTML = `
  <button class="info-btn">Подробнее</button>
`;

el.querySelector('.info-btn').addEventListener('click', () => {
  console.log('Открытие карточки объекта');
});

При динамическом создании большого количества маркеров предпочтительнее использовать делегирование событий:

el.addEventListener('click', (e) => {
  if (e.target.classList.contains('info-btn')) {
    console.log('Клик по кнопке внутри маркера');
  }
});

HTML-контент в интеграции с состоянием карты

HTML-слои часто синхронизируются с состоянием карты: масштабом, центром, выбранными объектами.

map.on('move', () => {
  const center = map.getCenter();

  document.getElementById('center-info').innerHTML = `
    Широта: ${center.lat.toFixed(4)}<br>
    Долгота: ${center.lng.toFixed(4)}
  `;
});

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

HTML в слоях взаимодействия (Overlay UI)

Помимо popups и markers, HTML используется для построения полноценных overlay-интерфейсов: фильтров, панелей, легенд.

const panel = document.createElement('div');
panel.className = 'map-panel';

panel.innerHTML = `
  <h2>Фильтры</h2>
  <label>
    <input type="checkbox" id="filter-cafes">
    Кафе
  </label>
`;

document.body.appendChild(panel);

Такой интерфейс не привязан к координатам карты и существует как независимый слой поверх canvas.

Динамическое обновление HTML-контента

HTML в Mapbox GL JS часто обновляется в ответ на события данных или взаимодействия пользователя.

map.on('click', 'points-layer', (e) => {
  const feature = e.features[0];

  popup.setHTML(`
    <div>
      <h3>${feature.properties.name}</h3>
      <p>${feature.properties.description}</p>
    </div>
  `);

  popup.setLngLat(feature.geometry.coordinates).addTo(map);
});

В этом случае HTML становится частью реактивного интерфейса, зависящего от геоданных.

Использование шаблонов для HTML-контента

При усложнении интерфейса прямое формирование строк становится неудобным. Используются шаблонизаторы или функции генерации DOM.

function createPopupContent(data) {
  const wrapper = document.createElement('div');

  const title = document.createElement('h3');
  title.textContent = data.name;

  const desc = document.createElement('p');
  desc.textContent = data.description;

  wrapper.appendChild(title);
  wrapper.appendChild(desc);

  return wrapper;
}

popup.setDOMContent(createPopupContent(feature.properties));

Этот подход улучшает читаемость и снижает риск XSS.

Производительность HTML-слоёв

DOM-элементы в Mapbox GL JS живут поверх WebGL-контекста, что накладывает ограничения:

  • каждый marker — отдельный DOM node
  • reflow и repaint влияют на плавность карты
  • большое количество popups снижает FPS

Оптимизации:

  • минимизация числа DOM-элементов
  • переиспользование popup вместо создания новых
  • отказ от сложной вложенной верстки
  • lazy rendering контента

Координация HTML и координат карты

HTML-элементы в Mapbox GL JS позиционируются через проекцию географических координат в экранные.

const point = map.project([71.4304, 51.1284]);

marker.style.transform = `
  translate(${point.x}px, ${point.y}px)
`;

Хотя библиотека делает это автоматически, понимание механизма важно при создании кастомных overlay-систем.

HTML и фреймворки

При использовании React, Vue или Svelte HTML-контент часто переносится в компонентную модель. В этом случае Mapbox GL JS выступает как низкоуровневый слой отображения.

const popupNode = document.createElement('div');
root.render(<PopupComponent data={feature} />, popupNode);

popup.setDOMContent(popupNode);

Такой подход позволяет отделить географическую логику от UI-логики.

Ограничения HTML-контента в Mapbox GL JS

HTML-контент не интегрирован в WebGL-рендер напрямую, что накладывает архитектурные ограничения:

  • невозможность включения HTML в слои карты
  • отсутствие z-index на уровне геометрии карты
  • независимость от рендер-цикла WebGL

Это означает, что HTML используется исключительно как overlay-слой, а не как часть графического пайплайна карты.

Сочетание HTML и событий карты

HTML-элементы часто реагируют на события карты:

map.on('zoom', () => {
  document.getElementById('zoom-level').textContent =
    map.getZoom().toFixed(2);
});

Таким образом DOM становится панелью мониторинга состояния карты, синхронизированной в реальном времени с WebGL-рендером Mapbox GL JS.