В Mapbox GL JS обработка кликов по объектам строится вокруг событийной модели карты и слоя отрисовки в WebGL. Основной механизм взаимодействия — это определение геометрии под координатами клика и последующая работа с источниками данных (sources) и слоями (layers).
Ключевой метод для обработки кликов:
map.on('click', 'layer-id', (e) => {
console.log(e.features);
});
Здесь обработка привязывается непосредственно к слою, что позволяет получать только те объекты, которые визуально представлены на карте в рамках указанного слоя.
Обработка кликов может выполняться на нескольких уровнях:
map.on('click'))map.on('click', layerId, handler))queryRenderedFeaturesГлобальный обработчик используется для сценариев, где требуется анализ всей сцены:
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
console.log(features);
});
Метод queryRenderedFeatures возвращает все объекты,
попадающие под пиксельные координаты события.
В основе выбора объектов лежит пиксельная координата события мыши. Mapbox GL JS преобразует экранные координаты в проверку пересечения с отрисованными тайлами.
const features = map.queryRenderedFeatures(point, {
layers: ['cities-layer']
});
Фильтрация по слоям позволяет ограничить выборку конкретной логической группой объектов, что критично при сложных сценах с множеством перекрывающихся источников данных.
Для управления состоянием объектов без изменения исходного GeoJSON
применяется механизм feature-state. Он особенно полезен при
обработке кликов, когда требуется подсветка или изменение визуального
состояния.
Установка состояния:
map.setFeatureState(
{ source: 'cities', id: 123 },
{ selected: true }
);
Использование состояния в стиле слоя:
map.setPaintProperty('cities-layer', 'circle-color', [
'case',
['boolean', ['feature-state', 'selected'], false],
'#ff0000',
'#3388ff'
]);
Такой подход позволяет избегать перегенерации источников данных и обеспечивает высокую производительность при интерактивной работе.
При наличии нескольких объектов под курсором результат
queryRenderedFeatures возвращает массив, отсортированный по
порядку отрисовки.
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
const topFeature = features[0];
console.log(topFeature);
});
Первый элемент массива соответствует верхнему визуальному слою, что соответствует принципу painter’s algorithm.
Для более точного контроля применяется фильтрация по слоям или анализ геометрии:
const features = map.queryRenderedFeatures(e.point, {
layers: ['points-layer']
});
Вместо точечного попадания можно использовать небольшую область вокруг курсора:
const bbox = [
[e.point.x - 5, e.point.y - 5],
[e.point.x + 5, e.point.y + 5]
];
const features = map.queryRenderedFeatures(bbox);
Такой подход улучшает UX при работе с мелкими объектами, особенно на мобильных устройствах.
При обработке кликов часто требуется различать типы геометрии:
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
features.forEach((f) => {
switch (f.geometry.type) {
case 'Point':
console.log('точка');
break;
case 'LineString':
console.log('линия');
break;
case 'Polygon':
console.log('полигон');
break;
}
});
});
Такое разделение позволяет строить разные сценарии взаимодействия для каждого типа объектов.
Mapbox GL JS использует DOM-события поверх canvas, поэтому требуется управление propagation:
map.on('click', 'layer-id', (e) => {
e.originalEvent.stopPropagation();
});
Это предотвращает срабатывание глобального обработчика карты при клике по слою.
При необходимости более сложной логики используется комбинация пиксельного запроса и геометрических вычислений.
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
const filtered = features.filter((f) => {
return f.properties.population > 100000;
});
console.log(filtered);
});
Таким образом реализуется бизнес-логика поверх визуальной выборки.
Изменение курсора при наведении на интерактивные объекты выполняется
через события mouseenter и mouseleave:
map.on('mouseenter', 'cities-layer', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = '';
});
Такая индикация повышает предсказуемость взаимодействия с картой.
Один из стандартных сценариев — отображение всплывающей информации:
map.on('click', 'cities-layer', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const description = e.features[0].properties.name;
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
});
При работе с координатами требуется учитывать возможное копирование массива, так как Mapbox может мутировать исходные данные.
При добавлении слоёв динамически обработчики необходимо пересоздавать или регистрировать после добавления слоя:
map.addLayer({
id: 'dynamic-layer',
type: 'circle',
source: 'dynamic-source'
});
map.on('click', 'dynamic-layer', (e) => {
console.log(e.features);
});
Отсутствие слоя в момент регистрации события приводит к отсутствию реакции на клики.
Порядок добавления слоёв напрямую влияет на результат выбора объектов. Верхние слои перекрывают нижние как визуально, так и логически при обработке событий.
Для управления приоритетом используется:
map.moveLayer('layer-id', 'before-layer-id');
или
map.moveLayer('layer-id');
Контроль порядка слоёв позволяет точно определять, какой объект будет выбран при клике.
При больших наборах данных критично минимизировать объём вычислений:
layers в
queryRenderedFeaturesfeature-state вместо пересоздания
GeoJSONclick в пользу layer-based
событийmap.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['optimized-layer']
});
});
Событие click в Mapbox GL JS фактически является
комбинацией mousedown и mouseup. При смещении
курсора событие может не срабатывать.
Для различения используется логика:
let startPoint = null;
map.on('mousedown', (e) => {
startPoint = e.point;
});
map.on('mouseup', (e) => {
const dx = Math.abs(e.point.x - startPoint.x);
const dy = Math.abs(e.point.y - startPoint.y);
if (dx < 3 && dy < 3) {
const features = map.queryRenderedFeatures(e.point);
console.log(features);
}
});
Такой подход позволяет отделять клики от перетаскивания карты.
При использовании кластеризации поведение кликов меняется: сначала определяется кластер, затем дочерние объекты.
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point);
const clusterId = features[0].properties.cluster_id;
map.getSource('points').getClusterExpansionZoom(clusterId, (err, zoom) => {
map.easeTo({
center: features[0].geometry.coordinates,
zoom
});
});
});
Такой механизм обеспечивает переход от агрегированных данных к детализации.
Слои могут динамически ограничивать интерактивность:
map.setFilter('cities-layer', ['==', ['get', 'active'], true]);
Это влияет и на визуализацию, и на результат
queryRenderedFeatures, что позволяет синхронизировать
логику кликов с состоянием данных.