В Mapbox GL JS взаимодействие с кликом по карте строится на системе
событий, где ключевым является map.on('click', ...). Именно
через обработчик клика реализуется логика закрытия всплывающих
элементов, сброса выделений и управления временными слоями.
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
});
Метод queryRenderedFeatures позволяет определить, был ли
клик по объекту слоя. При отсутствии объектов под курсором формируется
сценарий «клик по пустой области», который чаще всего используется как
триггер для закрытия UI-сущностей.
Всплывающие окна (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();
}
});
В этом сценарии закрытие происходит только при отсутствии объектов целевого слоя под курсором, что исключает преждевременное скрытие при взаимодействии с интерактивными элементами.
Часто используется поведение переключателя: первый клик открывает всплывающее окно, повторный — закрывает его.
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, так как срабатывает один раз в начале
взаимодействия.
Дополнительный канал управления состоянием реализуется через обработку клавиатуры.
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
popup.remove();
}
});
Такой механизм часто используется совместно с закрытием по клику, формируя многоканальную модель управления интерфейсом.
При использовании динамических источников (GeoJSON,
Vector Tiles) закрытие popup может зависеть от обновления
данных.
map.on('sourcedata', (e) => {
if (e.sourceId === 'places' && e.isSourceLoaded) {
popup.remove();
}
});
Событие sourcedata позволяет синхронизировать визуальные
элементы с жизненным циклом источников.
В системах, где одновременно используются 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 координаты позволяет не
разделять десктопные и мобильные сценарии.
При наличии 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Комбинация этих механизмов обеспечивает детерминированное управление жизненным циклом всплывающих элементов в Mapbox GL JS и позволяет строить предсказуемые интерфейсы поверх интерактивной карты.