Позиционирование попапов

В MapLibre GL JS попапы являются DOM-элементами, привязанными к координатам карты или к конкретным объектам слоя. Их позиционирование определяется системой экранных координат, преобразуемых из географических, а также набором параметров якоря, смещения и поведения при изменении состояния карты. Корректная работа с попапами требует понимания того, как библиотека рассчитывает их размещение относительно слоя тайлов и текущего матричного преобразования карты.

Каждый popup существует в двух системах координат:

  • географические координаты (longitude, latitude)
  • экранные координаты (pixel space)

При добавлении popup через setLngLat([lng, lat]) происходит преобразование в пиксельную систему с учётом текущего масштаба, центра карты, наклона и вращения.

new maplibregl.Popup()
  .setLngLat([30.5, 50.45])
  .setText('Точка интереса')
  .addTo(map);

В момент рендеринга карта вычисляет позицию якоря, после чего DOM-элемент popup помещается в overlay-контейнер поверх canvas.

Якорь (anchor) как основа позиционирования

Ключевым механизмом управления расположением popup является свойство anchor. Оно определяет, какой точкой всплывающего окна будет привязка к координате.

Основные значения:

  • center — центр popup привязан к координате
  • top — нижняя часть popup находится над точкой
  • bottom — верхняя часть popup находится под точкой
  • left — правый край popup указывает на точку
  • right — левый край popup указывает на точку
  • комбинации (top-left, top-right, bottom-left, bottom-right)
new maplibregl.Popup({
  anchor: 'bottom',
})
  .setLngLat([30.5, 50.45])
  .setText('Anchored popup')
  .addTo(map);

Механизм anchor влияет не только на визуальное положение, но и на расчёт автоматического смещения при приближении к границам viewport.

Автоматическое вычисление anchor

Если anchor не задан, библиотека выполняет автоматический выбор на основе доступного пространства вокруг точки. Алгоритм анализирует:

  • расстояние до краёв viewport
  • размеры popup
  • текущий zoom level
  • направление возможного перекрытия

В результате popup стремится оставаться полностью видимым без выхода за границы экрана.

Это поведение особенно заметно при размещении точек рядом с краями карты, где popup может «переворачиваться» вверх или вниз.

Смещение popup (offset)

Параметр offset позволяет вручную корректировать позицию popup относительно точки привязки. Он может быть задан:

  • числом (смещение по всем осям одинаковое)
  • объектом с указанием сторон
  • массивом пиксельных значений
new maplibregl.Popup({
  offset: 20
})
  .setLngLat([30.5, 50.45])
  .setText('Offset popup')
  .addTo(map);

Более точная форма управления:

new maplibregl.Popup({
  offset: {
    top: [0, 10],
    bottom: [0, -10],
    left: [10, 0],
    right: [-10, 0]
  }
});

Каждое значение задаёт смещение относительно конкретного anchor-состояния. Это важно при создании кастомных popup с нестандартной геометрией (например, с хвостом или асимметричным блоком).

Привязка к координатам и слоям

Popup может быть привязан как к произвольной координате, так и к feature слоя:

map.on('click', 'cities-layer', (e) => {
  const coordinates = e.features[0].geometry.coordinates.slice();

  new maplibregl.Popup()
    .setLngLat(coordinates)
    .setHTML('<strong>Город</strong>')
    .addTo(map);
});

При работе с GeoJSON слоями важно учитывать, что координаты могут быть копиями массива, особенно при работе с многополигональными структурами.

Поведение при перемещении карты

Popup автоматически пересчитывает своё положение при:

  • zoom
  • pan
  • rotate
  • pitch

Каждое изменение матрицы карты вызывает перерасчёт screen projection.

Это означает, что popup не фиксируется в DOM относительно окна браузера, а привязан к географической точке через постоянную трансформацию.

При резких изменениях масштаба возможны кратковременные смещения, связанные с пересчётом тайлового слоя.

Перенос popup в видимую область

Если popup выходит за пределы viewport, библиотека применяет алгоритм auto-padding и repositioning. Он включает:

  • изменение anchor
  • смещение offset
  • временное панорамирование карты (в некоторых конфигурациях)

Это поведение контролируется через параметры карты (padding), которые влияют на допустимую область размещения UI-элементов.

Взаимодействие с rotation и pitch

При включённом наклоне карты (pitch) и вращении (bearing) popup остаётся ориентированным к экрану, а не к географической плоскости.

Это означает:

  • popup не наклоняется вместе с картой
  • его позиция корректируется в screen space
  • anchor пересчитывается в проекции 3D → 2D

Особенно важно при больших значениях pitch, когда перспектива сильно искажает расстояния.

Кастомные popup и DOM-структура

Popup в MapLibre GL JS представляет собой DOM-узел со следующей структурой:

  • контейнер popup
  • content wrapper
  • tip (стрелка, если включена)
  • close button (опционально)

Можно переопределить содержимое:

new maplibregl.Popup({ closeButton: false })
  .setHTML(`
    <div class="custom-popup">
      <h3>Объект</h3>
      <p>Описание точки</p>
    </div>
  `)
  .setLngLat([30.5, 50.45])
  .addTo(map);

Стилизация влияет на расчёт размеров, а значит и на автоматический выбор anchor.

Поведение при динамическом обновлении содержимого

При изменении содержимого popup через setHTML или setText происходит:

  • перерасчёт bounding box
  • пересмотр anchor при включённом auto-mode
  • возможное изменение offset компенсации

Это особенно критично при асинхронной подгрузке данных (например, API-запросах), когда размеры popup неизвестны заранее.

Работа с multiple popups

Одновременное отображение нескольких popup требует учёта перекрытий. В стандартной конфигурации каждый popup:

  • не знает о других popup
  • рассчитывает позицию независимо
  • может перекрывать соседние элементы

Для управления слоями используются CSS z-index и стратегия группировки UI-элементов поверх карты.

Производительность и перерасчёты

Позиционирование popup связано с постоянными recalculation циклами при движении карты. Основные источники нагрузки:

  • пересчёт projection matrix
  • обновление DOM transform
  • recalculation anchor logic
  • repaint layout при изменении размеров

При большом количестве popup рекомендуется:

  • минимизировать сложность DOM внутри popup
  • избегать частого обновления содержимого
  • использовать кластеризацию точек вместо множества popup

Поведение в пределах мирового повторения (world wrapping)

При использовании бесшовного мира карта создаёт копии координат по оси longitude. Popup всегда привязывается к ближайшей копии координаты, чтобы избежать резких скачков при переходе через ±180°.

Это поведение обеспечивает визуальную стабильность при глобальном масштабировании и перемещении.

Контроль смещения относительно центра карты

Popup может участвовать в автоцентрировании карты, если включено соответствующее поведение (map.easeTo, flyTo в связке с popup). В таких случаях:

  • карта может смещаться для видимости popup
  • учитываются padding значения
  • применяется smooth animation

Это используется при сценариях «показать объект и сразу выделить его информацией».

Практические особенности anchor-логики

Выбор anchor напрямую зависит от:

  • плотности объектов на карте
  • положения точки относительно viewport
  • размера popup
  • наличия UI-слоёв (controls, legends)

Неправильная конфигурация anchor часто приводит к:

  • перекрытию важных элементов
  • частичной обрезке popup
  • визуальной нестабильности при pan/zoom

Поэтому фиксированный anchor предпочтителен в интерфейсах с предсказуемым layout, тогда как auto-anchor — в интерактивных аналитических картах.

Итоговая модель позиционирования

Механизм позиционирования popup в MapLibre GL JS можно рассматривать как комбинацию трёх слоёв:

  • географическая привязка (lng/lat)
  • экранная проекция (matrix transformation)
  • UI-логика (anchor + offset + auto-fit)

Эти уровни работают синхронно, обеспечивая стабильное отображение popup в условиях постоянного изменения матрицы карты и пользовательских взаимодействий.