Mapbox GL JS предоставляет низкоуровневую модель событий, основанную на взаимодействиях пользователя с картой. Drag-события формируют основу интерактивного поведения: перемещение карты, перетаскивание маркеров и кастомных объектов, синхронизация состояния интерфейса с жестами пользователя.
Drag-события в Mapbox GL JS строятся вокруг жизненного цикла жеста перетаскивания:
Эта модель применяется как к самой карте, так и к отдельным интерактивным объектам.
Основной принцип заключается в том, что любое drag-взаимодействие — это последовательность событий, отражающих изменения состояния pointer-устройства.
Карта в Mapbox GL JS по умолчанию поддерживает перетаскивание (pan). При движении мыши или пальца инициируются события:
dragstart — пользователь начал перемещение картыdrag — карта активно перемещаетсяdragend — пользователь отпустил кнопку мыши или
палецmap.on('dragstart', () => {
console.log('Начало перетаскивания карты');
});
map.on('drag', () => {
console.log('Карта перемещается');
});
map.on('dragend', () => {
console.log('Перетаскивание завершено');
});
События drag карты применяются для:
В Mapbox GL JS существует важное разделение:
drag* — инициируются жестами пользователяmove* — инициируются любым изменением камеры (включая
программное управление)События move:
movestartmovemoveendТаким образом:
Это различие критично при построении реактивных интерфейсов.
Одним из наиболее частых сценариев является drag маркеров. Для этого используется объект Marker с включённой опцией draggable.
const marker = new mapboxgl.Marker({
draggable: true
})
.setLngLat([30.5, 50.5])
.addTo(map);
После активации draggable доступны события:
dragstartdragdragendmarker.on('dragstart', () => {
console.log('Маркер начал перемещение');
});
marker.on('drag', () => {
const lngLat = marker.getLngLat();
console.log('Текущие координаты:', lngLat);
});
marker.on('dragend', () => {
const finalPosition = marker.getLngLat();
console.log('Финальная позиция:', finalPosition);
});
Хотя Mapbox GL JS не предоставляет прямого drag API для слоёв (layers), реализуется собственная логика через:
map.on('mousedown')map.on('mousemove')map.on('mouseup')map.queryRenderedFeatureslet isDragging = false;
let draggedFeatureId = null;
map.on('mousedown', 'points-layer', (e) => {
isDragging = true;
draggedFeatureId = e.features[0].id;
map.getCanvas().style.cursor = 'grabbing';
});
map.on('mousemove', (e) => {
if (!isDragging) return;
const coords = [e.lngLat.lng, e.lngLat.lat];
// обновление источника данных
const source = map.getSource('points-source');
const data = source._data;
data.features = data.features.map(f => {
if (f.id === draggedFeatureId) {
f.geometry.coordinates = coords;
}
return f;
});
source.setData(data);
});
map.on('mouseup', () => {
isDragging = false;
draggedFeatureId = null;
map.getCanvas().style.cursor = '';
});
Этот подход позволяет реализовать полноценное drag-поведение для GeoJSON-объектов.
Одним из ключевых аспектов drag-событий является работа с географическими координатами.
Mapbox GL JS автоматически предоставляет:
lngLat — географические координатыpoint — экранные координатыmap.on('mousemove', (e) => {
const screenPoint = e.point;
const geoPoint = e.lngLat;
console.log(screenPoint); // пиксели
console.log(geoPoint); // долгота/широта
});
При реализации drag логика обычно опирается на lngLat,
так как она независима от масштаба.
Drag-события могут вызывать высокую частоту обновлений (десятки раз в секунду). Это требует оптимизации.
setDatalet lastUpdate = 0;
map.on('mousemove', (e) => {
if (!isDragging) return;
const now = Date.now();
if (now - lastUpdate < 16) return; // ~60 FPS
lastUpdate = now;
updateFeaturePosition(e.lngLat);
});
function updateFrame(lngLat) {
requestAnimationFrame(() => {
updateFeaturePosition(lngLat);
});
}
Это снижает нагрузку на главный поток и улучшает плавность взаимодействия.
Во время drag карты изменяется камера (camera transform). Внутренне это включает:
map.on('dragend', () => {
const center = map.getCenter();
const zoom = map.getZoom();
const bearing = map.getBearing();
saveState({ center, zoom, bearing });
});
Такая логика применяется для:
Mapbox GL JS позволяет ограничивать область перемещения карты:
const bounds = [
[29.0, 49.0],
[31.0, 51.0]
];
map.setMaxBounds(bounds);
Это влияет и на drag:
После завершения drag может продолжаться инерционное движение (momentum). Оно генерирует:
movemoveendно не drag события.
Это важно при разделении логики:
let startPoint = null;
map.on('mousedown', (e) => {
startPoint = e.lngLat;
});
map.on('mousemove', (e) => {
if (!startPoint) return;
drawRectangle(startPoint, e.lngLat);
});
map.on('mouseup', () => {
startPoint = null;
});
Используется в:
Во время drag можно динамически фильтровать данные:
map.on('drag', () => {
const center = map.getCenter();
map.setFilter('points-layer', [
'distance',
['get', 'coordinates'],
center
]);
});
При проектировании drag-интеракций часто выделяют отдельный слой логики:
Пример состояния:
const dragState = {
active: false,
featureId: null,
startLngLat: null
};
Такой подход упрощает масштабирование интерактивных функций.
На touch-устройствах drag события работают через pointer events абстракцию.
Особенности:
Рекомендуется: