Zoom события

В системе MapLibre GL JS масштабирование карты реализовано как часть общей модели трансформации transform, где изменение zoom влияет на вычисление тайлов, перерисовку слоёв и обновление геометрии на canvas/WebGL. Все события, связанные с изменением масштаба, отражают жизненный цикл этого процесса: начало взаимодействия, промежуточные изменения и завершение анимации или пользовательского жеста.

Модель масштабирования и уровни zoom

Значение zoom в MapLibre GL JS — непрерывная величина (floating point), а не дискретный шаг. Это означает, что между целыми уровнями (например, 5 и 6) существуют промежуточные состояния:

  • zoom 5.0 — базовый уровень тайлов
  • zoom 5.3 — частично масштабированная сцена
  • zoom 5.8 — почти следующий уровень детализации
  • zoom 6.0 — переход к новому набору тайлов

Такой подход позволяет реализовать плавное масштабирование без «скачков» при смене тайлов.

Ключевые методы работы с масштабом:

map.getZoom();        // текущее значение zoom
map.setZoom(10);      // установка масштаба
map.zoomTo(12);       // с анимацией
map.easeTo({ zoom: 8 });
map.fitBounds(bounds);

Основные zoom-события

MapLibre GL JS предоставляет несколько событий, связанных с изменением масштаба карты:

zoomstart

Срабатывает в момент начала изменения zoom, независимо от источника:

  • колесо мыши
  • pinch жест на тач-устройствах
  • программный вызов с анимацией
map.on('zoomstart', () => {
    console.log('Начало изменения масштаба');
});

Особенность: событие вызывается один раз на цикл масштабирования, даже если внутри происходит множество промежуточных обновлений.


zoom

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

map.on('zoom', () => {
    console.log('Текущий zoom:', map.getZoom());
});

Характеристики:

  • вызывается многократно во время анимации или жеста
  • синхронизировано с рендер-циклом WebGL
  • может вызываться десятки раз в секунду

Это событие критично для задач, требующих динамической реакции на масштаб: изменение размеров маркеров, адаптация UI, управление плотностью данных.


zoomend

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

map.on('zoomend', () => {
    console.log('Масштабирование завершено:', map.getZoom());
});

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

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

Источники zoom-изменений

Zoom может изменяться разными способами, и это влияет на характер событий.

Пользовательские взаимодействия

  1. Колёсико мыши (wheel)
  2. Pinch-жест на сенсорных экранах
  3. Двойной клик (zoom in)

В этом случае события zoom сопровождаются низкоуровневым событием:

map.on('wheel', (e) => {
    // e.originalEvent содержит нативное событие колесика
});

Важно учитывать, что wheel не равен zoom, а лишь может его инициировать.


Программные изменения

map.zoomTo(14);
map.easeTo({ zoom: 9 });
map.setZoom(11);

Различие:

  • setZoom — мгновенное изменение
  • easeTo — анимация с интерполяцией
  • zoomTo — аналог easeTo с заданной целью

При анимации генерируются все события жизненного цикла: zoomstart → zoom → zoomend.


Связь zoom с move событиями

Zoom в MapLibre GL JS не изолирован — он является частью общей трансформации камеры. Поэтому при масштабировании также возникают события движения:

  • movestart
  • move
  • moveend

При изменении zoom без изменения центра (например, pinch на месте) всё равно происходят move события, так как матрица трансформации обновляется.


Пример комплексного отслеживания

map.on('movestart', () => {
    console.log('Начало движения/масштабирования');
});

map.on('zoomstart', () => {
    console.log('Начало zoom');
});

map.on('zoom', () => {
    const z = map.getZoom();
    console.log('zoom:', z.toFixed(2));
});

map.on('zoomend', () => {
    console.log('Завершение zoom:', map.getZoom());
});

map.on('moveend', () => {
    console.log('Завершение трансформации карты');
});

Частота вызовов и производительность

Событие zoom может вызываться с высокой частотой (до 60 fps). Это требует аккуратного подхода к обработке:

  • избегать тяжёлых вычислений внутри обработчика
  • не выполнять запросы к API на каждый tick
  • использовать debounce/throttle для сетевых операций

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

let timeout;

map.on('zoom', () => {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
        console.log('Финальное значение zoom для логики:', map.getZoom());
    }, 150);
});

Использование requestAnimationFrame

Для синхронизации с рендер-циклом WebGL:

map.on('zoom', () => {
    requestAnimationFrame(() => {
        updateUI(map.getZoom());
    });
});

Это позволяет избежать рассинхронизации между DOM и canvas.


Zoom и источники данных

Изменение zoom напрямую влияет на:

  • выбор тайлов в raster/vector источниках
  • видимость слоёв (minzoom, maxzoom)
  • кластеризацию данных
  • уровень детализации символов

Пример слоя с ограничением zoom:

map.addLayer({
    id: 'cities',
    type: 'circle',
    source: 'points',
    minzoom: 5,
    maxzoom: 12
});

При выходе за диапазон слой перестаёт участвовать в рендере, даже если zoom событие продолжает происходить.


Взаимодействие с inertia и easing

При завершении жестов MapLibre GL JS может продолжать движение камеры по инерции. В этом случае:

  • zoomend не вызывается до полной остановки
  • zoom продолжает обновляться
  • moveend фиксирует завершение всей анимации

Контроль границ zoom

MapLibre GL JS поддерживает ограничения:

const map = new maplibregl.Map({
    container: 'map',
    style: 'style.json',
    minZoom: 3,
    maxZoom: 18
});

При достижении границ:

  • zoom больше не увеличивается/уменьшается
  • события zoom могут продолжать вызываться в попытках жеста
  • фактическое значение остается фиксированным

Сравнение zoom с другими трансформациями

Zoom — лишь одна из составляющих камеры:

  • center — географическая позиция
  • zoom — масштаб
  • bearing — поворот
  • pitch — наклон

Изменение любого из этих параметров может инициировать move и рендер-пересчёт, но zoom дополнительно влияет на выбор уровня тайлов и детализацию геометрии.


Практическая модель событийного цикла

Типичный цикл масштабирования выглядит следующим образом:

  1. Пользователь начинает жест (wheel/pinch)
  2. zoomstart
  3. многократные zoom
  4. обновление move
  5. завершение жеста
  6. инерция (если есть)
  7. zoomend
  8. moveend

Каждый этап соответствует обновлению внутреннего состояния transform и перерасчёту WebGL сцены.