В MapLibre GL JS попап (Popup) представляет собой
всплывающее HTML-окно, которое может быть связано с координатами карты
или конкретным DOM-элементом маркера. На практике привязка попапа к
маркеру означает синхронизацию двух сущностей: географической точки
(LngLat) и визуального представления маркера
(Marker).
Маркер может выступать как контейнер события, инициирующего открытие
попапа, либо как якорь, к которому попап «прикрепляется» через механизм
setLngLat().
Маркер в MapLibre GL JS создаётся через
new maplibregl.Marker(). Он может содержать кастомный
HTML-элемент, что позволяет формировать сложные визуальные
компоненты.
const markerElement = document.createElement('div');
markerElement.className = 'custom-marker';
const marker = new maplibregl.Marker({
element: markerElement,
anchor: 'bottom'
})
.setLngLat([37.6173, 55.7558])
.addTo(map);
Параметр anchor управляет точкой привязки маркера
относительно координат, что критично для корректного позиционирования
попапа.
Попап создаётся независимо от маркера и может быть переиспользован или создан для каждого маркера отдельно.
const popup = new maplibregl.Popup({
closeButton: true,
closeOnClick: false,
offset: 25
}).setHTML(`
<div>
<h3>Объект</h3>
<p>Описание маркера</p>
</div>
`);
Ключевые параметры:
closeButton — отображение кнопки закрытияcloseOnClick — закрытие при клике по картеoffset — смещение относительно точки привязкиПростейший способ связывания маркера и попапа реализуется через
setPopup(). В этом случае MapLibre автоматически управляет
открытием и закрытием.
marker.setPopup(popup);
После этого попап открывается при клике на маркер и закрывается при повторном взаимодействии или клике вне области, в зависимости от настроек карты.
Более гибкий подход предполагает ручное управление жизненным циклом попапа через обработчики событий маркера.
marker.getElement().addEventListener('click', () => {
popup
.setLngLat(marker.getLngLat())
.addTo(map);
});
В этом случае попап не привязан к маркеру напрямую, а позиционируется
динамически через setLngLat().
При работе с массивом данных каждый маркер может иметь собственный попап с уникальным содержимым.
const points = [
{ coords: [37.6173, 55.7558], title: 'Москва' },
{ coords: [30.3351, 59.9343], title: 'Санкт-Петербург' }
];
points.forEach(point => {
const popup = new maplibregl.Popup({ offset: 20 })
.setHTML(`<strong>${point.title}</strong>`);
new maplibregl.Marker()
.setLngLat(point.coords)
.setPopup(popup)
.addTo(map);
});
Каждый маркер получает независимый экземпляр попапа, что исключает конфликт состояния.
При использовании кастомных элементов важно учитывать, что событие клика должно быть назначено именно на DOM-узел маркера.
const el = document.createElement('div');
el.className = 'icon-marker';
const marker = new maplibregl.Marker(el)
.setLngLat([37.62, 55.75])
.addTo(map);
const popup = new maplibregl.Popup({ offset: 30 })
.setText('Информация об объекте');
el.addEventListener('click', (e) => {
e.stopPropagation();
popup
.setLngLat(marker.getLngLat())
.addTo(map);
});
Использование stopPropagation() предотвращает конфликт с
глобальными обработчиками карты.
Попап по умолчанию позиционируется относительно координаты маркера,
но визуальное совпадение часто требует корректировки через
offset.
const popup = new maplibregl.Popup({
offset: {
bottom: [0, -20],
top: [0, 20]
}
});
Такой формат позволяет задавать разные смещения в зависимости от положения попапа относительно точки привязки.
Поведение попапов зависит от параметров карты и самого попапа. При интеграции с маркерами часто используется следующая комбинация:
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [37.6173, 55.7558],
zoom: 10
});
const popup = new maplibregl.Popup({
closeOnClick: false,
closeButton: true
});
Параметр closeOnClick: false позволяет удерживать попап
открытым при взаимодействии с картой, что полезно при сложных
интерфейсах.
Попап может использоваться повторно для разных маркеров без пересоздания экземпляра. В этом случае обновляется содержимое и позиция.
const popup = new maplibregl.Popup();
function showFeaturePopup(lngLat, data) {
popup
.setLngLat(lngLat)
.setHTML(`<div>${data.name}</div>`)
.addTo(map);
}
Такой подход снижает количество DOM-операций и улучшает производительность при большом количестве объектов.
При удалении маркера из карты попап не удаляется автоматически, если
он был добавлен вручную через addTo(map). В таких случаях
требуется синхронизация жизненного цикла:
marker.remove();
popup.remove();
При использовании setPopup() MapLibre самостоятельно
управляет состоянием попапа при удалении маркера.
По умолчанию несколько попапов могут быть открыты одновременно. Для ограничения этого поведения применяется ручное управление состоянием:
let activePopup = null;
marker.getElement().addEventListener('click', () => {
if (activePopup) {
activePopup.remove();
}
activePopup = new maplibregl.Popup()
.setLngLat(marker.getLngLat())
.setText('Данные объекта')
.addTo(map);
});
Такой подход обеспечивает единственный активный попап на карте.
Хотя маркеры часто создаются вручную, аналогичный принцип
используется при работе с GeoJSON-слоями. В этом случае попап
привязывается не к Marker, а к координатам фичи:
map.on('click', 'layer-id', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const description = e.features[0].properties.description;
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
});
Логика привязки остаётся идентичной: попап фиксируется в пространстве
через LngLat, а не через DOM-элемент.
Корректная привязка попапов к маркерам требует согласованности трёх элементов: географических координат, якоря маркера и смещения попапа. Несоответствие этих параметров приводит к визуальному смещению всплывающего окна относительно точки интереса, особенно при кастомных иконках или изменении масштаба карты.