События перемещения

В интерактивных картографических приложениях пользователь постоянно изменяет положение карты: перетаскивает её мышью, перемещает пальцем на сенсорных устройствах, программно изменяет центр карты или выполняет анимационный переход к другой области. Для отслеживания подобных действий в Leaflet предусмотрен набор специальных событий перемещения.

События перемещения позволяют:

  • реагировать на начало и окончание движения карты;
  • отслеживать процесс перемещения в реальном времени;
  • загружать данные только для видимой области;
  • синхронизировать несколько карт между собой;
  • обновлять элементы интерфейса в зависимости от текущего положения карты;
  • выполнять ленивую загрузку объектов.

Основными событиями являются:

  • movestart
  • move
  • moveend
  • dragstart
  • drag
  • dragend

Каждое из них решает собственную задачу и применяется в различных сценариях.


Событие movestart

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

Причинами его возникновения могут быть:

  • перетаскивание карты пользователем;
  • программный вызов setView();
  • выполнение panTo();
  • анимация перемещения;
  • инерционное движение после отпускания карты.

Пример:

map.on('movestart', function () {
    console.log('Началось перемещение карты');
});

При запуске этого обработчика карта ещё не изменила своё положение окончательно, однако процесс движения уже начался.

Часто событие используется для:

  • отображения индикатора загрузки;
  • подготовки к обновлению данных;
  • временного отключения тяжёлых вычислений.

Например:

map.on('movestart', function () {
    document.getElementById('status').textContent =
        'Загрузка данных...';
});

Событие move

Событие move генерируется непрерывно во время перемещения карты.

Это наиболее часто вызываемое событие среди всех событий движения.

Пример:

map.on('move', function () {
    console.log('Карта перемещается');
});

Во время одного перетаскивания событие может возникать десятки или сотни раз.

Получение текущего центра карты:

map.on('move', function () {
    const center = map.getCenter();

    console.log(center.lat, center.lng);
});

Такой подход позволяет в режиме реального времени отображать координаты текущего центра.

Пример обновления интерфейса:

map.on('move', function () {
    const center = map.getCenter();

    document.getElementById('lat').textContent =
        center.lat.toFixed(6);

    document.getElementById('lng').textContent =
        center.lng.toFixed(6);
});

Особенности события move

Поскольку событие вызывается очень часто, внутри обработчика нежелательно размещать тяжёлые операции.

Плохой пример:

map.on('move', function () {
    loadThousandsOfObjects();
});

Во время движения такой код может значительно снизить производительность.

Более правильный вариант:

map.on('moveend', function () {
    loadThousandsOfObjects();
});

Событие move лучше использовать для:

  • обновления текста;
  • отображения координат;
  • изменения небольших визуальных элементов;
  • синхронизации интерфейса.

Событие moveend

Событие moveend возникает после полного завершения перемещения карты.

Пример:

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

Это одно из наиболее важных событий в Leaflet.

Обычно именно здесь выполняются:

  • загрузка данных с сервера;
  • фильтрация объектов;
  • кластеризация;
  • обновление статистики;
  • вычисление видимой области.

Получение текущих границ карты:

map.on('moveend', function () {
    const bounds = map.getBounds();

    console.log(bounds);
});

Загрузка данных после перемещения

Распространённый сценарий — загрузка объектов только для текущей видимой области.

Пример:

map.on('moveend', function () {

    const bounds = map.getBounds();

    fetch(`/api/points?bbox=${
        bounds.toBBoxString()
    }`)
    .then(response => response.json())
    .then(data => {
        console.log(data);
    });

});

В данном случае запрос выполняется только после завершения движения карты.

Такой подход уменьшает:

  • нагрузку на сервер;
  • объём передаваемых данных;
  • количество лишних запросов.

Получение новой позиции карты

После завершения перемещения часто требуется узнать новый центр карты.

Пример:

map.on('moveend', function () {

    const center = map.getCenter();

    console.log(
        'Широта:',
        center.lat
    );

    console.log(
        'Долгота:',
        center.lng
    );

});

Метод getCenter() возвращает объект LatLng.

Доступные свойства:

center.lat
center.lng

Событие dragstart

Событие dragstart относится исключительно к ручному перетаскиванию карты пользователем.

Пример:

map.on('dragstart', function () {
    console.log('Началось перетаскивание');
});

Важно понимать разницу между событиями:

Событие Когда возникает
movestart Любое начало перемещения
dragstart Начало ручного перетаскивания

Если карта была перемещена через код:

map.panTo([55.75, 37.61]);

то:

movestart   → да
dragstart   → нет

Событие drag

Событие drag возникает непрерывно во время ручного перемещения карты.

Пример:

map.on('drag', function () {
    console.log('Карта перетаскивается');
});

Срабатывание происходит только при взаимодействии пользователя.

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

Получение координат центра:

map.on('drag', function () {

    const center = map.getCenter();

    console.log(center);

});

Событие dragend

Событие dragend возникает после завершения ручного перетаскивания.

Пример:

map.on('dragend', function () {
    console.log('Перетаскивание завершено');
});

Типичная область применения:

  • сохранение позиции карты;
  • отправка аналитики;
  • обновление данных;
  • синхронизация состояния приложения.

Например:

map.on('dragend', function () {

    const center = map.getCenter();

    localStorage.setItem(
        'mapLat',
        center.lat
    );

    localStorage.setItem(
        'mapLng',
        center.lng
    );

});

Последовательность событий при перетаскивании

Во время обычного ручного перемещения карта генерирует события в следующем порядке:

dragstart
movestart

drag
move
drag
move
drag
move
...

dragend
moveend

Таким образом:

  • drag* описывают действия пользователя;
  • move* описывают изменение положения карты вообще.

Перемещение через panTo

Метод panTo() выполняет плавный переход к новым координатам.

Пример:

map.panTo([59.9386, 30.3141]);

При этом будут вызваны:

movestart
move
move
move
...
moveend

События перетаскивания отсутствуют.


Перемещение через setView

Метод setView() изменяет центр карты.

Пример:

map.setView(
    [48.8566, 2.3522],
    12
);

Если происходит изменение положения карты, Leaflet генерирует события движения.

Например:

map.on('moveend', function () {
    console.log('Центр обновлён');
});

Отслеживание изменения видимой области

После каждого завершённого перемещения можно получать текущие границы карты.

Пример:

map.on('moveend', function () {

    const bounds =
        map.getBounds();

    const north =
        bounds.getNorth();

    const south =
        bounds.getSouth();

    const east =
        bounds.getEast();

    const west =
        bounds.getWest();

    console.log(
        north,
        south,
        east,
        west
    );

});

Подобный подход широко применяется в:

  • геоинформационных системах;
  • сервисах поиска объектов;
  • навигационных приложениях;
  • системах мониторинга транспорта.

Синхронизация нескольких карт

Иногда требуется, чтобы одна карта повторяла движения другой.

Пример:

map1.on('move', function () {

    map2.setView(
        map1.getCenter(),
        map1.getZoom(),
        {
            animate: false
        }
    );

});

Более безопасный вариант использует флаг блокировки:

let syncing = false;

map1.on('move', function () {

    if (syncing) return;

    syncing = true;

    map2.setView(
        map1.getCenter(),
        map1.getZoom(),
        {
            animate: false
        }
    );

    syncing = false;

});

Без такой проверки возможно возникновение бесконечного цикла событий.


Сохранение позиции карты между посещениями

Часто необходимо восстанавливать положение карты после перезагрузки страницы.

Сохранение:

map.on('moveend', function () {

    const center =
        map.getCenter();

    localStorage.setItem(
        'lat',
        center.lat
    );

    localStorage.setItem(
        'lng',
        center.lng
    );

    localStorage.setItem(
        'zoom',
        map.getZoom()
    );

});

Восстановление:

const lat =
    localStorage.getItem('lat');

const lng =
    localStorage.getItem('lng');

const zoom =
    localStorage.getItem('zoom');

if (lat && lng && zoom) {

    map.setView(
        [lat, lng],
        zoom
    );

}

Комбинирование событий перемещения

Несколько событий могут использоваться одновременно.

Пример:

map.on('movestart', function () {
    console.log('Старт');
});

map.on('move', function () {
    console.log('Движение');
});

map.on('moveend', function () {
    console.log('Финиш');
});

Результат:

Старт
Движение
Движение
Движение
Движение
Финиш

Подобная схема позволяет реализовать полный контроль над жизненным циклом перемещения карты.


Удаление обработчиков

Для прекращения отслеживания событий используется метод off().

Пример:

function onMove() {
    console.log('move');
}

map.on('move', onMove);

Удаление:

map.off('move', onMove);

Удаление всех обработчиков конкретного события:

map.off('move');

Удаление всех событий объекта карты:

map.off();

Практические рекомендации

Событие move

Подходит для:

  • отображения координат центра;
  • синхронизации интерфейса;
  • обновления небольших элементов страницы.

Не рекомендуется для:

  • тяжёлых вычислений;
  • сетевых запросов;
  • массовой обработки данных.

Событие moveend

Подходит для:

  • загрузки объектов;
  • выполнения запросов к серверу;
  • фильтрации данных;
  • обновления маркеров.

События dragstart, drag, dragend

Используются тогда, когда необходимо реагировать именно на действия пользователя, а не на любые изменения положения карты.

События movestart, move, moveend

Используются для отслеживания всех вариантов перемещения независимо от того, были они инициированы пользователем или программным кодом.