Привязка попапов к маркерам

Базовая модель взаимодействия маркера и попапа

В 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

Простейший способ связывания маркера и попапа реализуется через 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-элементу маркера

При использовании кастомных элементов важно учитывать, что событие клика должно быть назначено именно на 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-элемент.


Практика согласованного позиционирования

Корректная привязка попапов к маркерам требует согласованности трёх элементов: географических координат, якоря маркера и смещения попапа. Несоответствие этих параметров приводит к визуальному смещению всплывающего окна относительно точки интереса, особенно при кастомных иконках или изменении масштаба карты.