Содержимое popup

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


Базовые способы задания содержимого

Popup поддерживает два основных метода установки содержимого: текстовый и HTML.

Текстовое содержимое

Метод setText используется для отображения безопасного строкового контента без интерпретации HTML.

const popup = new mapboxgl.Popup()
  .setLngLat([30.5, 50.5])
  .setText('Простой текст внутри popup')
  .addTo(map);

Особенности текстового режима:

  • любые HTML-теги экранируются
  • исключается возможность XSS через вставку разметки
  • подходит для коротких подписей и идентификаторов

HTML-содержимое

Метод setHTML позволяет задавать полноценную разметку, включая вложенные элементы, стили и структуру.

const popup = new mapboxgl.Popup()
  .setLngLat([30.5, 50.5])
  .setHTML(`
    <div class="popup-content">
      <h3>Объект</h3>
      <p>Описание объекта на карте</p>
    </div>
  `)
  .addTo(map);

Использование HTML расширяет возможности отображения:

  • структурированные карточки объектов
  • таблицы характеристик
  • изображения и иконки
  • интерактивные элементы (кнопки, ссылки)

Формирование содержимого через DOM-элементы

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

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

const title = document.createElement('h3');
title.textContent = 'Название объекта';

const description = document.createElement('p');
description.textContent = 'Подробное описание';

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

const popup = new mapboxgl.Popup()
  .setLngLat([30.5, 50.5])
  .setDOMContent(container)
  .addTo(map);

Метод setDOMContent особенно полезен при сложной логике формирования интерфейса.


Динамическое содержимое на основе данных

Часто popup формируется на основе свойств GeoJSON-объектов или данных слоя.

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

  new mapboxgl.Popup()
    .setLngLat(feature.geometry.coordinates)
    .setHTML(`
      <div>
        <strong>${feature.properties.name}</strong>
        <p>${feature.properties.description}</p>
      </div>
    `)
    .addTo(map);
});

Ключевой принцип — связывание feature.properties с шаблоном отображения.


Использование шаблонов и функций генерации

При работе с большим количеством объектов целесообразно вынести генерацию содержимого в отдельную функцию.

function createPopupContent(properties) {
  return `
    <div class="popup-card">
      <h4>${properties.title}</h4>
      <p>${properties.text}</p>
      <span>Рейтинг: ${properties.rating}</span>
    </div>
  `;
}

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

  new mapboxgl.Popup()
    .setLngLat(feature.geometry.coordinates)
    .setHTML(createPopupContent(feature.properties))
    .addTo(map);
});

Такой подход упрощает поддержку и масштабирование интерфейса.


Асинхронная загрузка содержимого

Popup может формироваться на основе данных, полученных из внешних API.

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

  const response = await fetch(`/api/object/${feature.properties.id}`);
  const data = await response.json();

  new mapboxgl.Popup()
    .setLngLat(feature.geometry.coordinates)
    .setHTML(`
      <div>
        <h3>${data.name}</h3>
        <p>${data.details}</p>
      </div>
    `)
    .addTo(map);
});

В таких сценариях учитывается задержка загрузки и возможные состояния ошибки.


Безопасность HTML-содержимого

Использование setHTML требует контроля входных данных, поскольку прямое внедрение строк может привести к XSS-уязвимостям.

Типовые меры:

  • экранирование пользовательского ввода
  • использование whitelist полей properties
  • отказ от вставки необработанного HTML из внешних источников
function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}
.setHTML(`
  <div>
    <p>${escapeHtml(feature.properties.comment)}</p>
  </div>
`)

Стилизация содержимого popup

Содержимое popup не ограничивается структурой — важную роль играет CSS-оформление.

.popup-card {
  font-family: sans-serif;
  padding: 10px;
  max-width: 200px;
}

.popup-card h4 {
  margin: 0 0 5px;
  font-size: 14px;
}

.popup-card p {
  margin: 0;
  font-size: 12px;
  color: #555;
}

Mapbox GL JS добавляет собственную обёртку .mapboxgl-popup-content, которую можно переопределять:

.mapboxgl-popup-content {
  border-radius: 8px;
  padding: 12px;
}

Интерактивные элементы внутри popup

HTML-содержимое позволяет размещать элементы управления.

.setHTML(`
  <div>
    <h4>Объект</h4>
    <button oncl ick="handleClick()">Действие</button>
  </div>
`)

Более корректный подход — привязка событий после добавления popup в DOM:

const popup = new mapboxgl.Popup()
  .setLngLat(coords)
  .setHTML(`
    <div>
      <button class="action-btn">Нажать</button>
    </div>
  `)
  .addTo(map);

popup.getElement().querySelector('.action-btn')
  .addEventListener('click', () => {
    console.log('Событие обработано');
  });

Работа с повторным использованием содержимого

Для оптимизации можно переиспользовать DOM-узлы или кэшировать шаблоны.

const popupCache = new Map();

function getPopupContent(id, properties) {
  if (popupCache.has(id)) return popupCache.get(id);

  const el = document.createElement('div');
  el.innerHTML = `<strong>${properties.name}</strong>`;

  popupCache.set(id, el);
  return el;
}

Производительность при массовом создании popup

При большом количестве объектов следует избегать частого создания сложных DOM-структур.

Рекомендации:

  • минимизация HTML-разметки
  • использование setText при отсутствии необходимости в структуре
  • переиспользование popup-инстансов
  • отказ от тяжёлых операций внутри обработчиков событий
const popup = new mapboxgl.Popup();

map.on('mousemove', (e) => {
  const feature = map.queryRenderedFeatures(e.point)[0];
  if (!feature) return;

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

Привязка содержимого к состоянию карты

Popup может отражать состояние карты в момент взаимодействия: масштаб, фильтры слоёв, видимые объекты.

map.on('click', (e) => {
  const zoom = map.getZoom();

  new mapboxgl.Popup()
    .setLngLat(e.lngLat)
    .setHTML(`<p>Текущий масштаб: ${zoom}</p>`)
    .addTo(map);
});

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