В MapLibre GL JS всплывающие элементы реализуются через класс
Popup. Он позволяет отображать произвольный HTML-контент в
заданной географической точке или привязке к экранному объекту
(например, маркеру или координатам слоя).
Ключевая особенность Popup — его независимость от
DOM-карты: окно создаётся как отдельный слой поверх карты и
синхронизируется с её проекцией.
Основная инициализация:
const popup = new maplibregl.Popup()
.setLngLat([37.6173, 55.7558])
.setHTML("<h3>Москва</h3><p>Столица</p>")
.addTo(map);
Метод .setLngLat() фиксирует географическую позицию, а
.setHTML() задаёт содержимое.
Popup поддерживает несколько способов наполнения контентом:
HTML через строку
popup.setHTML("<strong>Объект</strong><br/>Описание");
DOM-элемент
const div = document.createElement("div");
div.innerText = "Контент через DOM";
popup.setDOMContent(div);
Использование DOM предпочтительно при динамических интерфейсах, так как позволяет подключать обработчики событий.
Наиболее распространённый сценарий — привязка всплывающего окна к маркеру.
const marker = new maplibregl.Marker()
.setLngLat([37.6173, 55.7558])
.addTo(map);
const popup = new maplibregl.Popup({ offset: 25 })
.setHTML("Информация о точке");
marker.setPopup(popup);
При клике на маркер всплывающее окно открывается автоматически.
Параметр offset позволяет смещать окно относительно
точки привязки, что предотвращает перекрытие маркера.
Конструктор Popup принимает объект конфигурации:
new maplibregl.Popup({
closeButton: true,
closeOnClick: true,
closeOnMove: false,
anchor: "bottom",
offset: 10,
className: "custom-popup"
});
closeButton Отображает кнопку закрытия.
closeOnClick Закрывает окно при клике по карте.
closeOnMove Закрывает окно при перемещении карты.
anchor Определяет точку привязки всплывающего окна относительно координаты:
topbottomleftrighttop-lefttop-rightbottom-leftbottom-rightPopup часто используется совместно с событиями карты, особенно при работе со слоями.
Пример отображения информации по клику на объект слоя:
map.on("click", "cities-layer", (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const name = e.features[0].properties.name;
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(`<strong>${name}</strong>`)
.addTo(map);
});
Метод e.features доступен при использовании
интерактивных слоёв с queryRenderedFeatures.
Tooltip-поведение реализуется через событие mouseenter и
mouseleave.
let popup;
map.on("mouseenter", "points-layer", (e) => {
map.getCanvas().style.cursor = "pointer";
const coordinates = e.features[0].geometry.coordinates.slice();
const title = e.features[0].properties.title;
popup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false
})
.setLngLat(coordinates)
.setHTML(title)
.addTo(map);
});
map.on("mouseleave", "points-layer", () => {
map.getCanvas().style.cursor = "";
if (popup) popup.remove();
});
Такой подход создаёт эффект «подсказки», не требующий явного клика.
Popup можно обновлять без пересоздания:
popup.setHTML("Загрузка...");
fetch("/api/info")
.then(res => res.json())
.then(data => {
popup.setHTML(`<pre>${JSON.stringify(data, null, 2)}</pre>`);
});
Это важно для асинхронных сценариев, когда данные подгружаются после открытия окна.
Popup предоставляет методы управления жизненным циклом:
popup.addTo(map); // показать
popup.remove(); // скрыть
popup.isOpen(); // проверка состояния
Повторное использование одного экземпляра предпочтительнее создания множества окон, особенно при высокой частоте событий.
Точная настройка позиции достигается через offset.
const popup = new maplibregl.Popup({
offset: [0, -20]
});
Также допустим объектное задание:
offset: {
top: [0, 10],
bottom: [0, -10],
left: [10, 0],
right: [-10, 0]
}
Это позволяет учитывать направление стрелки всплывающего окна.
MapLibre не ограничивает количество одновременно открытых popup, однако на практике это может перегружать интерфейс.
Частый паттерн — хранение единственного активного окна:
let activePopup = null;
function showPopup(lngLat, html) {
if (activePopup) activePopup.remove();
activePopup = new maplibregl.Popup()
.setLngLat(lngLat)
.setHTML(html)
.addTo(map);
}
Для получения данных под курсором используется:
map.on("click", (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ["cities-layer"]
});
if (!features.length) return;
const feature = features[0];
new maplibregl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML(feature.properties.name)
.addTo(map);
});
Этот механизм обеспечивает точное связывание геометрии и UI-слоя.
Popup поддерживает HTML, что требует контроля входных данных.
Опасный пример:
popup.setHTML(userInput);
Без очистки возможно внедрение скриптов. Безопасный вариант — экранирование:
function escapeHtml(str) {
return str.replace(/[&<>"']/g, (m) => ({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
}[m]));
}
popup.setHTML(escapeHtml(userInput));
При сложных интерфейсах предпочтительнее setDOMContent,
исключающий интерпретацию HTML-строки.
Popup поддерживает пользовательские классы:
new maplibregl.Popup({
className: "dark-popup"
});
CSS:
.dark-popup .maplibregl-popup-content {
background: #1e1e1e;
color: #ffffff;
border-radius: 8px;
padding: 10px;
}
.dark-popup .maplibregl-popup-tip {
border-top-color: #1e1e1e;
}
Такой подход позволяет полностью интегрировать popup в дизайн приложения.
Опция trackPointer делает всплывающее окно следящим за
курсором:
new maplibregl.Popup({
closeButton: false,
closeOnClick: false,
trackPointer: true
})
.setHTML("Информация")
.addTo(map);
Это используется в сценариях интерактивной аналитики и визуализации данных.
При частых событиях (hover по плотным слоям) создание новых popup на каждый event приводит к деградации производительности.
Оптимальный подход:
.setHTML().addTo(map) без необходимостиПример оптимизированной логики:
const popup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false
});
map.on("mousemove", "layer", (e) => {
const f = e.features[0];
popup
.setLngLat(f.geometry.coordinates)
.setHTML(f.properties.name);
if (!popup.isOpen()) popup.addTo(map);
});
При использовании кластеров всплывающие окна часто открываются на уровне раскрытия кластера:
map.on("click", "clusters", (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ["clusters"]
});
const clusterId = features[0].properties.cluster_id;
map.getSource("points").getClusterExpansionZoom(
clusterId,
(err, zoom) => {
if (err) return;
map.easeTo({
center: features[0].geometry.coordinates,
zoom
});
}
);
});
Popup здесь может использоваться как промежуточная информация о кластере.
Popup всегда отображается поверх слоёв карты, но порядок нескольких окон зависит от порядка добавления в DOM. Для контроля используется удаление и повторное добавление или единая точка управления активным состоянием.
При flyTo или easeTo popup автоматически
пересчитывает позицию, если он привязан к координатам. При сильных
анимациях может наблюдаться визуальное смещение, которое корректируется
после завершения кадра рендеринга карты.