Попапы в MapLibre GL JS строятся вокруг класса
maplibregl.Popup, который представляет всплывающее окно,
привязанное к координатам карты или к конкретному объекту слоя. Основной
принцип работы — создание экземпляра попапа, его конфигурация и
последующее связывание с координатами или геометрией объекта.
Типовой цикл выглядит так:
const popup = new maplibregl.Popup({
closeButton: true,
closeOnClick: true,
maxWidth: "300px"
})
.setLngLat([37.6173, 55.7558])
.setHTML("<h3>Москва</h3><p>Столица</p>")
.addTo(map);
Ключевые элементы:
setLngLat — фиксирует географическую позициюsetHTML — задаёт содержимое в виде строки HTMLaddTo(map) — добавляет попап на картуМетод setHTML применяется для быстрого рендеринга
содержимого попапа в виде строки. Это удобно при работе с простыми
шаблонами, где данные уже подготовлены.
popup.setHTML(`
<div class="popup">
<strong>${feature.properties.name}</strong>
<div>${feature.properties.description}</div>
</div>
`);
Однако использование строкового HTML требует осторожности. Любые данные, поступающие извне, должны быть предварительно очищены, поскольку попап напрямую вставляет HTML в DOM без дополнительной фильтрации.
Частые источники риска:
feature.propertiesКогда содержимое попапа выходит за рамки простого текста,
предпочтительнее использовать setDOMContent, позволяющий
передать готовый DOM-элемент.
const container = document.createElement("div");
container.className = "popup-container";
const title = document.createElement("h3");
title.textContent = feature.properties.name;
const description = document.createElement("p");
description.textContent = feature.properties.description;
container.appendChild(title);
container.appendChild(description);
popup.setDOMContent(container);
Преимущества:
textContentНаиболее распространённый сценарий — отображение попапа при клике по объекту слоя.
map.on("click", "cities-layer", (e) => {
const feature = e.features[0];
new maplibregl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML(`
<strong>${feature.properties.name}</strong>
`)
.addTo(map);
});
Важно учитывать:
interactive: true)e.featuresПри использовании кластеризации или плотных слоёв в одной точке может находиться несколько объектов. В этом случае необходимо явно выбирать нужный feature.
map.on("click", "points-layer", (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ["points-layer"]
});
if (!features.length) return;
const feature = features[0];
new maplibregl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML(`<div>${feature.properties.title}</div>`)
.addTo(map);
});
Для более сложных интерфейсов может быть реализован список объектов внутри одного попапа.
Создание нового попапа на каждый клик приводит к избыточной нагрузке. В большинстве случаев эффективнее переиспользовать один экземпляр.
const popup = new maplibregl.Popup({
closeButton: true,
closeOnClick: true
});
map.on("click", "cities-layer", (e) => {
const feature = e.features[0];
popup
.setLngLat(feature.geometry.coordinates)
.setHTML(`<strong>${feature.properties.name}</strong>`)
.addTo(map);
});
Такой подход:
MapLibre GL JS предоставляет несколько параметров, влияющих на поведение попапа:
const popup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false,
anchor: "top",
offset: 10
});
Основные параметры:
closeButton — отображение кнопки закрытияcloseOnClick — закрытие при клике по картеanchor — позиция относительно точки привязки (top,
bottom, left, right и др.)offset — смещение попапаПараметр anchor особенно важен при размещении попапов
над маркерами, чтобы избежать перекрытия элементов.
Попапы часто используются как контейнер для данных, которые подгружаются по API.
map.on("click", "cities-layer", async (e) => {
const feature = e.features[0];
const popup = new maplibregl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML("Загрузка...")
.addTo(map);
const response = await fetch(`/api/city/${feature.properties.id}`);
const data = await response.json();
popup.setHTML(`
<div>
<h3>${data.name}</h3>
<p>Население: ${data.population}</p>
</div>
`);
});
Ключевая особенность — возможность обновлять содержимое попапа после
его создания через повторный вызов setHTML или
setDOMContent.
Попапы используют собственную структуру DOM, в которую можно внедрять стили через CSS.
.maplibregl-popup-content {
font-family: Arial, sans-serif;
padding: 12px;
border-radius: 8px;
}
.popup-container h3 {
margin: 0 0 6px 0;
font-size: 16px;
}
.popup-container p {
margin: 0;
font-size: 13px;
color: #555;
}
Также можно управлять внешним контейнером попапа:
.maplibregl-popup {
max-width: 320px;
}
Использование setHTML подходит для:
Использование setDOMContent предпочтительно для:
Комбинирование подходов возможно, но обычно приводит к усложнению поддержки кода.
Так как попап является частью DOM, внутри него можно регистрировать события:
const container = document.createElement("div");
container.innerHTML = `
<button id="details-btn">Подробнее</button>
`;
container.querySelector("#details-btn").addEventListener("click", () => {
console.log("Переход к деталям объекта");
});
popup.setDOMContent(container);
Важно учитывать:
Помимо кликов, попапы часто используются при hover-сценариях.
map.on("mouseenter", "cities-layer", (e) => {
map.getCanvas().style.cursor = "pointer";
const feature = e.features[0];
popup
.setLngLat(feature.geometry.coordinates)
.setHTML(feature.properties.name)
.addTo(map);
});
map.on("mouseleave", "cities-layer", () => {
map.getCanvas().style.cursor = "";
popup.remove();
});
Особенности:
При работе с большим количеством объектов критично избегать создания
множества экземпляров Popup.
Подходы оптимизации:
textContent вместо
innerHTMLДополнительно:
mousemove без необходимостиПопапы часто привязываются к Marker:
const marker = new maplibregl.Marker()
.setLngLat([37.6, 55.75])
.addTo(map);
const popup = new maplibregl.Popup({ offset: 25 })
.setHTML("<strong>Точка интереса</strong>");
marker.setPopup(popup);
Особенность такого подхода:
Попап может служить реактивным контейнером:
function updatePopup(feature) {
popup.setHTML(`
<div>
<strong>${feature.properties.name}</strong>
<div>Обновлено: ${new Date().toLocaleTimeString()}</div>
</div>
`);
}
Такой подход используется при: