Drag события

MapLibre GL JS предоставляет низкоуровневые и высокоуровневые механизмы обработки перетаскивания, позволяя управлять как самим движением карты, так и пользовательскими интерактивными элементами. Drag-события играют ключевую роль в построении интерфейсов, где требуется точное отслеживание движения указателя и преобразование экранных координат в географические.


Основные принципы drag-механики

В основе drag-событий лежит обработка последовательности pointer-событий, которые MapLibre абстрагирует в единый поток взаимодействия:

  • начало взаимодействия (mousedown / touchstart)
  • изменение положения (mousemove / touchmove)
  • завершение взаимодействия (mouseup / touchend / touchcancel)

MapLibre объединяет эти события в единый API, который обеспечивает согласованное поведение на разных устройствах.

Ключевой объект — Map, который генерирует события, связанные с перетаскиванием карты и взаимодействием с слоями.


Встроенное перетаскивание карты

Перемещение карты в MapLibre GL JS реализуется через встроенный обработчик drag-pan. Он включается по умолчанию и управляется через методы взаимодействия:

map.dragPan.enable();
map.dragPan.disable();
map.dragPan.isEnabled();

Особенности dragPan

  • поддерживает мышь и сенсорные устройства
  • учитывает инерцию движения
  • автоматически преобразует экранные координаты в географические
  • взаимодействует с кинематикой камеры (Camera API)

При активном перетаскивании изменяется состояние камеры:

  • center — координаты центра карты
  • bearing — угол поворота (при включённом rotate drag)
  • pitch — наклон (если разрешён)

События drag карты

MapLibre генерирует набор событий, связанных с перетаскиванием:

movestart

Срабатывает в момент начала любого изменения положения карты.

move

Вызывается многократно во время перетаскивания.

moveend

Срабатывает после завершения движения.


Пример обработки drag-событий карты

map.on('movestart', () => {
    console.log('Перемещение началось');
});

map.on('move', () => {
    const center = map.getCenter();
    console.log(center);
});

map.on('moveend', () => {
    console.log('Перемещение завершено');
});

Drag объектов через события pointer

MapLibre GL JS не предоставляет встроенного drag-and-drop для слоёв, но реализует основу через события указателя:

  • mousedown / touchstart
  • mousemove / touchmove
  • mouseup / touchend

Эти события позволяют реализовать перемещение маркеров, кастомных слоёв и геометрий.


Преобразование координат при drag

Ключевая операция при реализации 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);
});

Реализация drag маркера

Типичный сценарий — перемещение маркера по карте.

Логика работы

  1. фиксируется начало drag
  2. сохраняется смещение между точкой клика и позицией объекта
  3. во время движения обновляется координата маркера
  4. на отпускании фиксируется новая позиция

Пример реализации

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);
});

Drag GeoJSON-объектов

Для слоёв типа circle, symbol, fill необходимо использовать hit-testing через:

map.queryRenderedFeatures(point, { layers: ['layer-id'] });

Алгоритм drag:

  • определение выбранного объекта
  • фиксация начальной геометрии
  • пересчёт координат при движении мыши
  • обновление источника данных (setData)

Пример перемещения точки GeoJSON

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;
});

dragPan и пользовательский drag: конфликт событий

При одновременной работе drag карты и drag объектов возникает конфликт:

  • карта начинает перемещаться вместо объекта
  • событие перехватывается dragPan

Решение — stopPropagation

canvas.addEventListener('mousedown', (e) => {
    e.stopPropagation();
});

Или временное отключение dragPan:

map.dragPan.disable();
map.dragPan.enable();

Drag с учётом инерции и плавности

При перемещении карты MapLibre использует физическую модель движения:

  • velocity
  • easing
  • momentum

При реализации кастомного drag важно избегать конфликтов с анимацией камеры.

Для синхронизации используется:

map.isMoving()
map.isZooming()

Drag и touch-события

На мобильных устройствах drag обрабатывается через touch API:

  • multi-touch может интерпретироваться как pinch-to-zoom
  • одиночное касание — drag

Особенности:

  • touchstart инициирует drag
  • touchmove обновляет позицию
  • touchend завершает взаимодействие

MapLibre автоматически нормализует touch-события в pointer-логику.


Drag и кастомные слои WebGL

При работе с custom layers drag может использоваться для:

  • перемещения камер в WebGL-сцене
  • управления 3D объектами
  • синхронизации внешнего рендера с картой

Основной принцип — привязка мировых координат к системе MapLibre через:

mapboxgl.MercatorCoordinate.fromLngLat(lngLat)

Оптимизация drag-производительности

При частых событиях mousemove важно минимизировать нагрузку:

  • избегать полного пересчёта GeoJSON
  • использовать requestAnimationFrame
  • кэшировать результат project/unproject

Пример оптимизации

let pending = false;

map.on('mousemove', (e) => {
    if (pending) return;

    pending = true;

    requestAnimationFrame(() => {
        const lngLat = map.unproject(e.point);
        updatePosition(lngLat);
        pending = false;
    });
});

Drag и события камеры

Перетаскивание напрямую связано с камерой MapLibre:

  • setCenter
  • setBearing
  • setPitch

При кастомных drag-интерфейсах часто используется прямое управление камерой:

map.setCenter(newCenter);

или плавное обновление:

map.easeTo({
    center: newCenter,
    duration: 0
});

Комплексные сценарии drag-взаимодействий

В реальных приложениях drag редко ограничивается одним объектом:

  • перемещение маркеров с привязкой к сетке
  • drag с ограничением области (bounds)
  • перетаскивание полигонов с пересчётом всех вершин
  • синхронизация drag между несколькими слоями

Ограничение области drag

Для ограничения перемещения используется проверка bounds:

const bounds = map.getBounds();

if (!bounds.contains(lngLat)) {
    return;
}

Drag и зум в одном жесте

При использовании trackpad или pinch жестов drag может комбинироваться с zoom:

  • drag изменяет центр
  • pinch изменяет масштаб
  • rotation gesture изменяет bearing

MapLibre объединяет это в unified gesture pipeline, сохраняя консистентность камеры.


Drag как основа интерактивных картографических интерфейсов

Механизм drag в MapLibre GL JS формирует фундамент для построения:

  • редакторов геоданных
  • систем маршрутизации
  • визуальных аналитических панелей
  • GIS-инструментов

Все взаимодействия с картой в конечном итоге сводятся к обработке движения указателя и преобразованию координат через drag-модель.