MapLibre GL JS предоставляет низкоуровневые и высокоуровневые механизмы обработки перетаскивания, позволяя управлять как самим движением карты, так и пользовательскими интерактивными элементами. Drag-события играют ключевую роль в построении интерфейсов, где требуется точное отслеживание движения указателя и преобразование экранных координат в географические.
В основе drag-событий лежит обработка последовательности pointer-событий, которые MapLibre абстрагирует в единый поток взаимодействия:
MapLibre объединяет эти события в единый API, который обеспечивает согласованное поведение на разных устройствах.
Ключевой объект — Map, который генерирует события, связанные с перетаскиванием карты и взаимодействием с слоями.
Перемещение карты в MapLibre GL JS реализуется через встроенный обработчик drag-pan. Он включается по умолчанию и управляется через методы взаимодействия:
map.dragPan.enable();
map.dragPan.disable();
map.dragPan.isEnabled();
При активном перетаскивании изменяется состояние камеры:
center — координаты центра картыbearing — угол поворота (при включённом rotate
drag)pitch — наклон (если разрешён)MapLibre генерирует набор событий, связанных с перетаскиванием:
movestartСрабатывает в момент начала любого изменения положения карты.
moveВызывается многократно во время перетаскивания.
moveendСрабатывает после завершения движения.
map.on('movestart', () => {
console.log('Перемещение началось');
});
map.on('move', () => {
const center = map.getCenter();
console.log(center);
});
map.on('moveend', () => {
console.log('Перемещение завершено');
});
MapLibre GL JS не предоставляет встроенного drag-and-drop для слоёв, но реализует основу через события указателя:
mousedown / touchstartmousemove / touchmovemouseup / touchendЭти события позволяют реализовать перемещение маркеров, кастомных слоёв и геометрий.
Ключевая операция при реализации drag объектов — преобразование экранных координат в географические.
Используются методы:
map.getCanvas().getBoundingClientRect()
map.unproject([x, y])
map.project([lng, lat])
map.on('mousemove', (e) => {
const lngLat = map.unproject([e.point.x, e.point.y]);
console.log(lngLat);
});
Типичный сценарий — перемещение маркера по карте.
let isDragging = false;
const marker = new maplibregl.Marker({
draggable: false
})
.setLngLat([30.5, 50.5])
.addTo(map);
const canvas = map.getCanvas();
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const point = [e.clientX - rect.left, e.clientY - rect.top];
const lngLat = marker.getLngLat();
const markerPixel = map.project(lngLat);
const offset = {
x: markerPixel.x - point[0],
y: markerPixel.y - point[1]
};
isDragging = true;
function onMove(eMove) {
if (!isDragging) return;
const x = eMove.clientX - rect.left + offset.x;
const y = eMove.clientY - rect.top + offset.y;
const newLngLat = map.unproject([x, y]);
marker.setLngLat(newLngLat);
}
function onUp() {
isDragging = false;
canvas.removeEventListener('mousemove', onMove);
canvas.removeEventListener('mouseup', onUp);
}
canvas.addEventListener('mousemove', onMove);
canvas.addEventListener('mouseup', onUp);
});
Для слоёв типа circle, symbol,
fill необходимо использовать hit-testing через:
map.queryRenderedFeatures(point, { layers: ['layer-id'] });
setData)let selectedFeature = null;
map.on('mousedown', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['points']
});
if (!features.length) return;
selectedFeature = features[0];
});
map.on('mousemove', (e) => {
if (!selectedFeature) return;
const newCoords = map.unproject(e.point);
const source = map.getSource('geojson-data');
const data = source._data;
data.features = data.features.map(f => {
if (f.id === selectedFeature.id) {
f.geometry.coordinates = [newCoords.lng, newCoords.lat];
}
return f;
});
source.setData(data);
});
map.on('mouseup', () => {
selectedFeature = null;
});
При одновременной работе drag карты и drag объектов возникает конфликт:
stopPropagationcanvas.addEventListener('mousedown', (e) => {
e.stopPropagation();
});
Или временное отключение dragPan:
map.dragPan.disable();
map.dragPan.enable();
При перемещении карты MapLibre использует физическую модель движения:
При реализации кастомного drag важно избегать конфликтов с анимацией камеры.
Для синхронизации используется:
map.isMoving()
map.isZooming()
На мобильных устройствах drag обрабатывается через touch API:
Особенности:
touchstart инициирует dragtouchmove обновляет позициюtouchend завершает взаимодействиеMapLibre автоматически нормализует touch-события в pointer-логику.
При работе с custom layers drag может использоваться для:
Основной принцип — привязка мировых координат к системе MapLibre через:
mapboxgl.MercatorCoordinate.fromLngLat(lngLat)
При частых событиях mousemove важно минимизировать
нагрузку:
let pending = false;
map.on('mousemove', (e) => {
if (pending) return;
pending = true;
requestAnimationFrame(() => {
const lngLat = map.unproject(e.point);
updatePosition(lngLat);
pending = false;
});
});
Перетаскивание напрямую связано с камерой MapLibre:
setCentersetBearingsetPitchПри кастомных drag-интерфейсах часто используется прямое управление камерой:
map.setCenter(newCenter);
или плавное обновление:
map.easeTo({
center: newCenter,
duration: 0
});
В реальных приложениях drag редко ограничивается одним объектом:
Для ограничения перемещения используется проверка bounds:
const bounds = map.getBounds();
if (!bounds.contains(lngLat)) {
return;
}
При использовании trackpad или pinch жестов drag может комбинироваться с zoom:
MapLibre объединяет это в unified gesture pipeline, сохраняя консистентность камеры.
Механизм drag в MapLibre GL JS формирует фундамент для построения:
Все взаимодействия с картой в конечном итоге сводятся к обработке движения указателя и преобразованию координат через drag-модель.