В Mapbox GL JS взаимодействие с картой строится на системе событий,
аналогичной DOM, но привязанной к географическому контексту. Основное
событие для обработки нажатий — click, которое
регистрируется через метод map.on.
map.on('click', (e) => {
console.log(e.lngLat);
});
Объект события содержит ключевую информацию:
lngLat — географические координаты точки кликаpoint — пиксельные координаты внутри контейнера
картыoriginalEvent — исходное DOM-событие браузераПиксельные координаты используются при работе с визуальными слоями, а географические — при взаимодействии с данными.
Одна из основных задач обработки клика — определение объектов,
находящихся под точкой взаимодействия. Для этого применяется метод
queryRenderedFeatures.
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
console.log(features);
});
Метод возвращает массив объектов GeoJSON, которые визуально присутствуют в текущем кадре карты.
При наличии нескольких слоёв часто требуется ограничить поиск конкретным слоем:
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['cities-layer']
});
console.log(features);
});
Такой подход позволяет отделить интерактивные объекты от фоновой графики.
Mapbox GL JS поддерживает event-driven модель для слоёв. При включении интерактивности слой может напрямую реагировать на события.
map.on('click', 'cities-layer', (e) => {
const feature = e.features[0];
console.log(feature.properties);
});
В этом режиме:
e.features содержит только объекты выбранного слояqueryRenderedFeatures не
требуетсяОдним из распространённых сценариев обработки клика является
отображение всплывающей информации через Popup.
map.on('click', 'cities-layer', (e) => {
const feature = e.features[0];
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`
<h3>${feature.properties.name}</h3>
<p>${feature.properties.description}</p>
`)
.addTo(map);
});
Ключевые моменты:
lngLatpropertiesaddToПри клике по карте не всегда попадает объект слоя. Это требует проверки результата выборки.
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['cities-layer']
});
if (!features.length) {
return;
}
const feature = features[0];
console.log(feature);
});
Такой подход предотвращает ошибки при обращении к несуществующим элементам массива.
Для интерактивных сценариев используется механизм состояния фич —
setFeatureState. Он позволяет изменять визуальные свойства
без пересоздания источника данных.
map.on('click', 'cities-layer', (e) => {
const feature = e.features[0];
map.setFeatureState(
{
source: 'cities-source',
id: feature.id
},
{
selected: true
}
);
});
Стилизация слоя может учитывать это состояние:
'circle-color': [
'case',
['boolean', ['feature-state', 'selected'], false],
'#ff0000',
'#3388ff'
]
Для управления состоянием часто требуется сброс предыдущего выделения.
let selectedId = null;
map.on('click', 'cities-layer', (e) => {
const feature = e.features[0];
if (selectedId) {
map.setFeatureState(
{ source: 'cities-source', id: selectedId },
{ selected: false }
);
}
selectedId = feature.id;
map.setFeatureState(
{ source: 'cities-source', id: selectedId },
{ selected: true }
);
});
Интерактивность часто дополняется изменением курсора для визуальной обратной связи.
map.on('mouseenter', 'cities-layer', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = '';
});
Такое поведение подчёркивает кликабельность объектов.
При использовании кластеризации данные группируются, и обработка клика требует дополнительного шага.
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['clusters']
});
const clusterId = features[0].properties.cluster_id;
map.getSource('points').getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) return;
map.easeTo({
center: features[0].geometry.coordinates,
zoom
});
});
});
Основные этапы:
cluster_idПри наличии нескольких слоёв важно учитывать порядок их рендеринга. События обрабатываются начиная с верхнего визуального слоя.
Для предотвращения конфликта взаимодействий используется логика проверки:
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
const target = features.find(f => f.layer.id === 'cities-layer');
if (!target) return;
console.log(target);
});
Mapbox GL JS унифицирует обработку pointer-событий, однако поведение на тач-устройствах требует учёта задержек и особенностей:
click может сопровождаться
touchendmap.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
if (!features.length) return;
console.log('touch/click interaction');
});
Часто обработка клика дополняется mousemove или
mouseenter для создания сложных интерфейсов.
map.on('mousemove', 'cities-layer', (e) => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('click', 'cities-layer', (e) => {
const feature = e.features[0];
console.log(feature);
});
Такой подход используется для построения интерактивных карт с динамическими подсказками и выделением объектов.
При усложнении интерфейса обработчики клика часто централизуются:
function handleFeatureClick(feature, lngLat) {
console.log(feature.properties);
}
map.on('click', 'cities-layer', (e) => {
handleFeatureClick(e.features[0], e.lngLat);
});
Это упрощает сопровождение логики и разделяет визуальный слой от бизнес-логики.
При необходимости анализа нескольких слоёв одновременно используется агрегированная выборка:
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
const grouped = features.reduce((acc, f) => {
acc[f.layer.id] = acc[f.layer.id] || [];
acc[f.layer.id].push(f);
return acc;
}, {});
console.log(grouped);
});
Такой подход позволяет обрабатывать сложные сцены, где несколько типов объектов пересекаются в одной точке.
stopPropagation в контексте MapboxВ отличие от DOM, Mapbox GL JS не использует классическую модель всплытия событий, однако контроль над обработкой достигается через логику внутри обработчиков:
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
if (features.length) {
// обработка приоритетного объекта
return;
}
console.log('click on empty map');
});
При работе с большим количеством данных критично минимизировать вычисления внутри обработчиков:
queryRenderedFeatures без
необходимостиОптимизация особенно важна при анимациях и частых обновлениях источников данных.
Клик не всегда попадает строго в пиксель объекта. Для повышения удобства взаимодействия используется расширение области выборки:
const bbox = [
[e.point.x - 5, e.point.y - 5],
[e.point.x + 5, e.point.y + 5]
];
const features = map.queryRenderedFeatures(bbox);
Такой подход создаёт “зону захвата” вокруг курсора, улучшая UX при мелких объектах.