Плавная анимация перемещения

В Mapbox GL JS управление картой строится вокруг камеры (camera), которая определяет текущий видимый участок карты: центр, масштаб, наклон и поворот. Плавная анимация перемещения камеры — ключевой механизм для создания интерактивных интерфейсов, навигационных систем и визуализаций данных, где резкие скачки ухудшают восприятие.


Базовые методы анимации камеры

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

flyTo — «перелёт» к точке

Метод flyTo реализует анимацию с эффектом полёта по дуге, имитируя перемещение камеры в трёхмерном пространстве.

map.flyTo({
  center: [37.6173, 55.7558],
  zoom: 12,
  speed: 1.2,
  curve: 1.42,
  essential: true
});

Ключевые параметры:

  • center — конечные координаты
  • zoom — целевой масштаб
  • speed — скорость анимации (влияет на длительность)
  • curve — кривизна траектории (чем выше, тем более «параболическая» траектория)
  • essential — признак важной анимации (не отключается настройками reduced motion)

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


easeTo — плавная линейная интерполяция

Метод easeTo используется для мягкого изменения параметров камеры без выраженной «дуги».

map.easeTo({
  center: [37.6173, 55.7558],
  zoom: 10,
  duration: 1500,
  easing: (t) => t
});

Основные параметры:

  • duration — длительность анимации в миллисекундах
  • easing — функция сглаживания (t от 0 до 1)
  • pitch, bearing, zoom, center — целевые параметры камеры

В отличие от flyTo, здесь поведение полностью определяется функцией easing, что позволяет точно контролировать динамику движения.

Пример более естественного ускорения и замедления:

map.easeTo({
  center: [37.6173, 55.7558],
  zoom: 13,
  duration: 2000,
  easing: (t) => t * (2 - t)
});

jumpTo — мгновенное перемещение

Метод jumpTo полностью отключает анимацию и моментально устанавливает состояние камеры:

map.jumpTo({
  center: [37.6173, 55.7558],
  zoom: 8
});

Используется в ситуациях, где анимация не требуется или может мешать:

  • синхронизация состояния интерфейса
  • восстановление сохранённой позиции
  • мгновенные переходы между слоями данных

Управление параметрами камеры

Плавная анимация в Mapbox GL JS охватывает не только центр и масштаб, но и пространственные характеристики сцены.

Zoom-анимация

map.easeTo({
  zoom: 14,
  duration: 1200
});

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


Поворот карты (bearing)

map.easeTo({
  bearing: 90,
  duration: 2000
});

Поворот используется для ориентации по направлению движения или выравнивания карты под интерфейс.


Наклон (pitch)

map.easeTo({
  pitch: 60,
  duration: 2000
});

Наклон активирует перспективную проекцию и часто используется в 3D-визуализациях зданий.


Функция easing и контроль интерполяции

Функция easing определяет характер изменения параметров во времени. Она принимает нормализованное значение t ∈ [0, 1] и возвращает преобразованное значение.

Типовые варианты:

Линейная интерполяция:

t => t

Ускорение:

t => t * t

Замедление:

t => 1 - Math.pow(1 - t, 3)

Комбинированная кривая:

t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2

Эти функции напрямую влияют на ощущение «плавности» движения камеры.


Прерывание и управление текущей анимацией

Любое новое изменение камеры автоматически прерывает текущую анимацию. Это поведение важно учитывать при построении сложных интерфейсов.

map.flyTo({ center: [30, 50], zoom: 5 });
map.easeTo({ center: [40, 60], zoom: 6 });

В этом случае второй вызов отменяет первый.

Для контроля состояния можно использовать события:

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

Также доступны события:

  • movestart
  • move
  • moveend
  • zoomstart
  • zoomend

Программная анимация через requestAnimationFrame

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

Основой служит метод map.setCenter, map.setZoom, вызываемые в цикле:

function animateTo(targetLng, targetLat, duration) {
  const start = performance.now();
  const startCenter = map.getCenter();

  function frame(time) {
    const t = (time - start) / duration;

    if (t >= 1) {
      map.setCenter([targetLng, targetLat]);
      return;
    }

    const lng = startCenter.lng + (targetLng - startCenter.lng) * t;
    const lat = startCenter.lat + (targetLat - startCenter.lat) * t;

    map.setCenter([lng, lat]);

    requestAnimationFrame(frame);
  }

  requestAnimationFrame(frame);
}

Такой подход позволяет реализовать кастомные кривые движения, синхронизацию с внешними системами и сложные сценарии анимации.


Интерполяция координат и проекция

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

Mapbox GL JS использует сферическую интерполяцию через Mercator-проекцию внутри движка, однако при кастомной реализации часто применяют:

function interpolate(a, b, t) {
  return a + (b - a) * t;
}

Для более точной географической интерполяции используется переход через проекцию:

const start = map.project([lng1, lat1]);
const end = map.project([lng2, lat2]);

const x = start.x + (end.x - start.x) * t;
const y = start.y + (end.y - start.y) * t;

const lngLat = map.unproject([x, y]);
map.setCenter(lngLat);

Синхронизация анимации с данными

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

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

Чрезмерная нагрузка в обработчиках move может приводить к падению производительности, поэтому вычисления обычно ограничиваются:

let ticking = false;

map.on('move', () => {
  if (!ticking) {
    requestAnimationFrame(() => {
      const center = map.getCenter();
      updateData(center);
      ticking = false;
    });
    ticking = true;
  }
});

Анимация между состояниями (state transition)

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

const states = {
  city: { center: [37.6, 55.7], zoom: 12 },
  country: { center: [100, 60], zoom: 3 }
};

function goToState(name) {
  map.easeTo({
    ...states[name],
    duration: 1800
  });
}

Такая архитектура упрощает управление сложными сценариями навигации.


Управление пользовательским взаимодействием во время анимации

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

Для ограничения взаимодействия можно временно отключить управление:

map.scrollZoom.disable();
map.dragPan.disable();

После завершения анимации управление восстанавливается:

map.once('moveend', () => {
  map.scrollZoom.enable();
  map.dragPan.enable();
});

Тонкая настройка плавности движения

Качество анимации зависит от сочетания нескольких параметров:

  • duration — слишком короткое значение создаёт резкие скачки
  • zoom — большие изменения масштаба требуют более высокой длительности
  • curve в flyTo — влияет на траекторию движения
  • easing — определяет субъективное восприятие плавности

Комбинация этих параметров позволяет адаптировать поведение карты под конкретный интерфейс: от навигационных систем до аналитических панелей и сторителлинговых визуализаций.