Закрытие по клику

В Mapbox GL JS взаимодействие с кликом по карте строится на системе событий, где ключевым является map.on('click', ...). Именно через обработчик клика реализуется логика закрытия всплывающих элементов, сброса выделений и управления временными слоями.

map.on('click', (e) => {
  const features = map.queryRenderedFeatures(e.point);
});

Метод queryRenderedFeatures позволяет определить, был ли клик по объекту слоя. При отсутствии объектов под курсором формируется сценарий «клик по пустой области», который чаще всего используется как триггер для закрытия UI-сущностей.


Закрытие Popup при клике вне объекта

Всплывающие окна (Popup) являются одним из основных интерактивных элементов Mapbox GL JS. Стандартная модель поведения предполагает их закрытие при клике вне связанного слоя или маркера.

const popup = new mapboxgl.Popup({
  closeButton: true,
  closeOnClick: false
});

Отключение closeOnClick переводит управление закрытием на уровень пользовательской логики.

Дальнейшая обработка клика:

map.on('click', (e) => {
  const features = map.queryRenderedFeatures(e.point, {
    layers: ['places-layer']
  });

  if (!features.length) {
    popup.remove();
  }
});

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


Закрытие Popup при повторном клике по объекту

Часто используется поведение переключателя: первый клик открывает всплывающее окно, повторный — закрывает его.

let activePopup = null;

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

  if (activePopup) {
    activePopup.remove();
    activePopup = null;
    return;
  }

  activePopup = new mapboxgl.Popup()
    .setLngLat(coordinates)
    .setHTML('<div>Информация об объекте</div>')
    .addTo(map);
});

Состояние активного popup хранится в переменной, что позволяет реализовать строгий контроль жизненного цикла UI-элемента.


Закрытие всех всплывающих окон при глобальном клике

При наличии нескольких независимых источников popup требуется централизованное управление их закрытием.

const popups = [];

function createPopup(lngLat, html) {
  const popup = new mapboxgl.Popup()
    .setLngLat(lngLat)
    .setHTML(html)
    .addTo(map);

  popups.push(popup);
  return popup;
}

map.on('click', (e) => {
  const features = map.queryRenderedFeatures(e.point);

  if (!features.length) {
    popups.forEach(p => p.remove());
    popups.length = 0;
  }
});

Очистка массива после удаления предотвращает накопление ссылок на неактивные экземпляры.


Разделение кликов по слоям и фону карты

При сложных стилях карта содержит несколько слоёв, и логика закрытия зависит от контекста взаимодействия.

map.on('click', (e) => {
  const buildingFeatures = map.queryRenderedFeatures(e.point, {
    layers: ['buildings']
  });

  const roadFeatures = map.queryRenderedFeatures(e.point, {
    layers: ['roads']
  });

  if (!buildingFeatures.length && !roadFeatures.length) {
    popup.remove();
  }
});

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


Использование stopPropagation в обработчиках слоёв

Mapbox GL JS обрабатывает события в иерархии слоёв, где клик по объекту слоя может всплывать до глобального обработчика карты.

map.on('click', 'places-layer', (e) => {
  e.originalEvent.cancelBubble = true;

  popup
    .setLngLat(e.features[0].geometry.coordinates)
    .setHTML('Объект')
    .addTo(map);
});

Подавление всплытия события предотвращает немедленное закрытие popup глобальным обработчиком клика.


Закрытие при перемещении карты

Иногда логика интерфейса требует закрытия всплывающих элементов при начале перемещения карты.

map.on('movestart', () => {
  popup.remove();
});

Событие movestart обеспечивает более предсказуемое поведение, чем move, так как срабатывает один раз в начале взаимодействия.


Закрытие по Escape и другим клавишам

Дополнительный канал управления состоянием реализуется через обработку клавиатуры.

document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    popup.remove();
  }
});

Такой механизм часто используется совместно с закрытием по клику, формируя многоканальную модель управления интерфейсом.


Синхронизация состояния UI с источниками данных

При использовании динамических источников (GeoJSON, Vector Tiles) закрытие popup может зависеть от обновления данных.

map.on('sourcedata', (e) => {
  if (e.sourceId === 'places' && e.isSourceLoaded) {
    popup.remove();
  }
});

Событие sourcedata позволяет синхронизировать визуальные элементы с жизненным циклом источников.


Контроль закрытия при hover-логике

В системах, где одновременно используются hover и click, закрытие требует учёта конфликтов состояний.

let hoveredFeatureId = null;

map.on('mouseleave', 'places-layer', () => {
  hoveredFeatureId = null;
  popup.remove();
});

При смешанной модели взаимодействия hover-логика часто дополняет click-логику, формируя единый механизм очистки интерфейса.


Централизованный менеджер всплывающих окон

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

class PopupManager {
  constructor() {
    this.popups = [];
  }

  open(popup) {
    this.popups.push(popup);
  }

  closeAll() {
    this.popups.forEach(p => p.remove());
    this.popups = [];
  }

  closeOnEmptyClick(map) {
    map.on('click', (e) => {
      const features = map.queryRenderedFeatures(e.point);
      if (!features.length) {
        this.closeAll();
      }
    });
  }
}

Такая структура устраняет дублирование логики и обеспечивает единый механизм очистки состояния интерфейса.


Обработка сенсорных событий

На мобильных устройствах поведение клика заменяется событиями touch, которые Mapbox GL JS обрабатывает через тот же слой событий.

map.on('touchstart', (e) => {
  const features = map.queryRenderedFeatures(e.point);

  if (!features.length) {
    popup.remove();
  }
});

Унификация логики через point координаты позволяет не разделять десктопные и мобильные сценарии.


Предотвращение конфликтов между UI-слоями

При наличии DOM-оверлеев поверх карты требуется координация закрытия между Mapbox GL JS и HTML-элементами.

map.on('click', (e) => {
  const panel = document.querySelector('.sidebar');

  const features = map.queryRenderedFeatures(e.point);

  if (!features.length && panel) {
    panel.classList.remove('open');
    popup.remove();
  }
});

Таким образом клик по пустой области синхронно закрывает как map-based элементы, так и DOM-интерфейсы.


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

Поведение закрытия по клику формируется как композиция нескольких уровней:

  • обработка клика по карте через map.on('click')
  • фильтрация объектов через queryRenderedFeatures
  • контроль состояния popup через ссылки на экземпляры
  • подавление событий внутри слоёв
  • дополнительные триггеры: клавиатура, движение карты, touch-события

Комбинация этих механизмов обеспечивает детерминированное управление жизненным циклом всплывающих элементов в Mapbox GL JS и позволяет строить предсказуемые интерфейсы поверх интерактивной карты.