События слоёв в Mapbox GL JS строятся на модели взаимодействия с отрисованными в WebGL объектами, где каждый слой может выступать источником событий указателя. Основной механизм основан на привязке обработчиков к идентификатору слоя и типу события, что позволяет реагировать на взаимодействие не с картой в целом, а с конкретной географической сущностью.
Слои в системе рендеринга Mapbox представляют собой абстракции над источниками данных (sources), и именно слой становится точкой привязки интерактивности. События не генерируются самими данными напрямую — они вычисляются через попадание курсора в область пикселей, соответствующих отрисованным геометриям слоя.
Основной паттерн обработки событий строится вокруг метода
map.on, где вторым аргументом указывается идентификатор
слоя.
map.on('click', 'cities-layer', (e) => {
console.log(e.features);
});
Такой обработчик срабатывает только в случае попадания события в
геометрию слоя cities-layer. Объект события содержит массив
features, включающий все объекты, удовлетворяющие попаданию
в пиксель события.
Ключевые типы событий, применяемые к слоям:
clickdblclickmouseentermouseleavemousemovemousedownmouseuptouchstarttouchendОпределение того, попадает ли курсор в слой, основано на функции выборки отрисованных объектов:
Эквивалентом низкоуровневой операции является:
map.queryRenderedFeatures(point, {
layers: ['cities-layer']
});
Событийная модель использует аналогичный механизм, но оптимизированный для интерактивного цикла рендера.
Эти события привязаны к переходу курсора между состояниями «внутри слоя» и «вне слоя».
map.on('mouseenter', 'cities-layer', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = '';
});
Семантика отличается от DOM-событий: переход фиксируется не по элементам HTML, а по результатам WebGL hit-testing.
Событие mousemove внутри слоя генерируется при каждом
движении курсора над геометрией слоя:
map.on('mousemove', 'cities-layer', (e) => {
const feature = e.features[0];
console.log(feature.properties);
});
Частота вызовов может быть высокой, поэтому обработка должна учитывать производительность, минимизируя вычисления внутри callback.
Событие click является центральным для взаимодействия с
объектами карты.
map.on('click', 'cities-layer', (e) => {
const feature = e.features[0];
const coordinates = feature.geometry.coordinates;
});
При совпадении нескольких объектов возвращается массив
features, отсортированный по визуальной приоритетности
слоёв и порядку отрисовки.
Особенность модели заключается в том, что событие не связано с DOM-элементами, а формируется на основе GPU-буфера.
Один и тот же источник данных может использоваться в нескольких слоях (fill, line, symbol), и события могут пересекаться.
map.on('click', ['fill-layer', 'line-layer'], (e) => {
console.log(e.features);
});
При этом:
features содержит объекты всех указанных слоёв;События слоёв часто используются вместе с состоянием объектов:
map.on('mousemove', 'cities-layer', (e) => {
if (e.features.length > 0) {
map.setFeatureState(
{ source: 'cities', id: e.features[0].id },
{ hover: true }
);
}
});
Состояние влияет на стили слоя через выражения:
'paint': {
'circle-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#ff0000',
'#3388ff'
]
}
Таким образом, события напрямую связываются с динамической визуализацией.
События mouseenter и mousemove не
гарантируют стабильную частоту вызовов, поскольку зависят от:
Повторные mousemove могут приходить без изменения
features, поэтому часто применяется сравнение
идентификаторов:
let lastId = null;
map.on('mousemove', 'cities-layer', (e) => {
const id = e.features[0]?.id;
if (id !== lastId) {
lastId = id;
}
});
События слоёв не используют классическое DOM-всплытие. Однако существует приоритет обработки:
map.on('click', ...)) получают
событие только при отсутствии совпадений слоёв.На мобильных устройствах используются аналогичные события:
map.on('touchstart', 'cities-layer', (e) => {
console.log(e.features);
});
Особенности:
touchstart часто заменяет click;События часто комбинируются с фильтрацией через filter
слоя:
map.setFilter('cities-layer', ['==', ['get', 'type'], 'capital']);
События автоматически учитывают только отфильтрованные фичи, так как фильтр применяется на этапе рендеринга.
События слоёв часто используются для привязки всплывающих окон:
map.on('click', 'cities-layer', (e) => {
new mapboxgl.Popup()
.setLngLat(e.features[0].geometry.coordinates)
.setHTML(e.features[0].properties.name)
.addTo(map);
});
Поведение зависит от точности геометрии и zoom-уровня, поскольку hit-test основан на пиксельной интерпретации.
При высокой плотности данных возникают ограничения:
mousemove;Типовой подход оптимизации:
let timeout;
map.on('mousemove', 'cities-layer', (e) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
// обработка
}, 50);
});
Map-level события:
map.on('click', (e) => {});
Layer-level события:
map.on('click', 'layer-id', (e) => {});
Различия:
features;При использовании composite sources событие может включать фичи из разных тайловых источников. В таком случае:
Интерактивность слоя определяется наличием:
map.on(..., layer, handler)interactive: true (в старых
конфигурациях)Без идентификаторов feature.id некоторые операции
setFeatureState становятся недоступными, хотя события
продолжают работать.
События слоёв генерируются после стадии рендера, где:
Это означает, что события всегда отражают уже отрисованное состояние, а не исходные данные напрямую.