События наведения курсора в Mapbox GL JS строятся вокруг модели
взаимодействия слоя карты и указателя мыши, где ключевыми механизмами
выступают события mouseenter, mouseleave и
mousemove, привязанные к конкретным слоям (layers). Эти
события позволяют реализовывать интерактивные сценарии: подсветку
объектов, отображение всплывающих окон, динамическое обновление
интерфейса и реакцию на перемещение курсора по геометрии.
Mapbox GL JS использует событийную модель, в которой взаимодействие с картой может быть привязано как к самой карте, так и к конкретным слоям. Для слоёв применяются специальные события, генерируемые при пересечении курсора с отрисованной геометрией.
Основной принцип заключается в том, что события возникают не на DOM-элементе, а на векторных данных, визуализированных в WebGL-контексте. Это делает взаимодействие более точным и зависящим от геометрии, а не от пикселей интерфейса.
Регистрация событий выполняется через:
map.on('mouseenter', 'layer-id', (e) => {
// обработка входа курсора в область слоя
});
Событие mouseenter срабатывает в момент, когда курсор
впервые пересекает визуализированный объект слоя. Повторные движения
внутри одного и того же объекта не вызывают повторного события.
Ключевая особенность заключается в том, что событие привязано к переходу состояния «вне слоя → внутри слоя».
Типичный сценарий использования — изменение состояния курсора и первичная инициализация интерактивного поведения:
map.on('mouseenter', 'countries-fill', (e) => {
map.getCanvas().style.cursor = 'pointer';
});
Событие содержит объект MapMouseEvent, включающий:
e.features — массив объектов, попавших под курсорe.lngLat — географические координатыe.point — пиксельные координаты относительно
canvasПри наличии фильтрации слоя (filter) событие учитывает
только отображаемые элементы.
Часто mouseenter используется как триггер для активации
подсветки:
map.on('mouseenter', 'regions', (e) => {
const featureId = e.features[0].id;
map.setFeatureState(
{ source: 'regions-source', id: featureId },
{ hover: true }
);
});
Событие mouseleave фиксирует момент, когда курсор
покидает область всех объектов слоя. Оно является логическим
продолжением mouseenter и завершает интерактивное
состояние.
Основное назначение — сброс визуальных эффектов и очистка временных состояний:
map.on('mouseleave', 'countries-fill', () => {
map.getCanvas().style.cursor = '';
});
В отличие от DOM-событий, mouseleave в Mapbox GL JS
срабатывает не при выходе за пределы контейнера карты, а именно при
выходе из геометрии слоя. Это позволяет разделять взаимодействие между
несколькими слоями.
Распространённый паттерн — сброс feature-state:
map.on('mouseleave', 'regions', (e) => {
if (e.features && e.features.length) {
map.setFeatureState(
{ source: 'regions-source', id: e.features[0].id },
{ hover: false }
);
}
});
При использовании большого количества объектов рекомендуется хранить идентификатор активного элемента в переменной состояния, чтобы избежать избыточных вызовов API.
Событие mousemove вызывается при каждом изменении
положения курсора над объектами слоя. Оно используется для динамических
сценариев, где требуется постоянное обновление состояния интерфейса.
В отличие от mouseenter и mouseleave,
mousemove может генерировать большое количество вызовов,
что делает важным контроль производительности.
Пример базового использования:
map.on('mousemove', 'places', (e) => {
const feature = e.features[0];
map.getCanvas().style.cursor = 'pointer';
});
Часто mousemove применяется для обновления всплывающих
окон:
const popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
map.on('mousemove', 'places', (e) => {
const feature = e.features[0];
popup
.setLngLat(e.lngLat)
.setHTML(`<strong>${feature.properties.name}</strong>`)
.addTo(map);
});
При интенсивной работе с mousemove важно учитывать
частоту вызовов. Mapbox GL JS не ограничивает поток событий, поэтому при
сложной логике возможны просадки производительности.
Различия между событиями определяются их семантикой и частотой вызова:
mouseenter — однократное срабатывание при входе в
геометрию слояmouseleave — однократное срабатывание при выходе из
геометрии слояmousemove — непрерывное срабатывание при перемещении
курсора внутри слояЭта модель позволяет разделять инициализацию состояния, его сброс и постоянное обновление.
Каждое событие слоя может возвращать массив features,
соответствующий объектам под курсором. Этот механизм основан на
внутреннем вызове queryRenderedFeatures.
При необходимости можно выполнить аналогичный запрос вручную:
map.on('mousemove', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['places']
});
});
Такой подход используется, когда требуется объединение данных из нескольких слоёв или более сложная фильтрация.
Для векторных источников оптимальным способом управления визуальными
эффектами является feature-state. Он позволяет изменять
стиль объекта без перерисовки всего слоя.
Типичная схема:
map.on('mousemove', 'regions', (e) => {
if (e.features.length > 0) {
map.setFeatureState(
{ source: 'regions-source', id: e.features[0].id },
{ hover: true }
);
}
});
map.on('mouseleave', 'regions', (e) => {
if (e.features.length > 0) {
map.setFeatureState(
{ source: 'regions-source', id: e.features[0].id },
{ hover: false }
);
}
});
В стиле слоя используется выражение:
paint: {
'fill-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#ff6b6b',
'#3388ff'
]
}
Интенсивные события движения требуют минимизации вычислений внутри обработчиков. Основные подходы оптимизации включают:
setFeatureStateПример контроля повторных обновлений:
let hoveredId = null;
map.on('mousemove', 'regions', (e) => {
const id = e.features[0].id;
if (hoveredId !== id) {
if (hoveredId !== null) {
map.setFeatureState(
{ source: 'regions-source', id: hoveredId },
{ hover: false }
);
}
hoveredId = id;
map.setFeatureState(
{ source: 'regions-source', id },
{ hover: true }
);
}
});
При наличии нескольких перекрывающихся слоёв порядок их определения влияет на то, какой слой получит события. Верхний визуально слой имеет приоритет при обработке событий курсора.
Это поведение связано с порядком рендеринга и Z-index в стиле карты.
Для точного контроля взаимодействий используется параметр
beforeId при добавлении слоя или управление порядком в
стиле:
map.addLayer(layerConfig, 'existing-layer-id');
Каждое событие содержит географические и экранные координаты:
lngLat — координаты в системе долготы и широтыpoint — координаты в пикселях canvasЭто позволяет синхронизировать географические данные с интерфейсными элементами:
map.on('mousemove', 'places', (e) => {
const { lng, lat } = e.lngLat;
const { x, y } = e.point;
});
Комбинация mouseenter, mousemove и
mouseleave формирует полный цикл взаимодействия:
mouseenter — активация состоянияmousemove — обновление данныхmouseleave — сброс состоянияТакая структура используется для построения интерактивных картографических интерфейсов с высокой отзывчивостью и предсказуемым поведением.