Управление временем анимации

В MapLibre GL JS временная ось анимации строится вокруг механизма перерисовки кадра, синхронизированного с requestAnimationFrame. Каждый визуальный переход — от изменения камеры до интерполяции свойств слоёв — выражается через дискретные кадры, где время выступает основным параметром интерполяции.

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


Базовый цикл анимации

Внутренний цикл рендеринга MapLibre GL JS основан на следующей модели:

  • запрос кадра через requestAnimationFrame
  • вычисление прошедшего времени
  • пересчёт состояния камеры и стиля
  • отрисовка кадра WebGL

Каждый цикл получает временную метку timestamp, которая используется для интерполяции значений:

function frame(timestamp) {
  map.triggerRepaint();
  requestAnimationFrame(frame);
}

Хотя прямое управление этим циклом недоступно, поведение анимации зависит от параметров переходов и камерных методов.


Временные параметры переходов камеры

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

easeTo

Метод задаёт плавный переход к новой позиции карты:

map.easeTo({
  center: [37.6173, 55.7558],
  zoom: 10,
  duration: 2000
});

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

  • duration — длительность анимации в миллисекундах
  • easing — функция сглаживания
  • animate — включение/выключение анимации

Функция easing определяет распределение времени:

easing: (t) => t * (2 - t)

Здесь t — нормализованное время от 0 до 1.


flyTo

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

map.flyTo({
  center: [30.3351, 59.9343],
  zoom: 12,
  speed: 1.2,
  curve: 1.42
});

Временная динамика определяется комбинацией:

  • speed — относительная скорость движения
  • curve — кривизна траектории
  • minZoom и maxDuration — ограничения длительности

В отличие от easeTo, здесь время не фиксировано напрямую, а вычисляется из расстояния и параметров скорости.


rotateTo и pitchTo

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

map.rotateTo(90, { duration: 1500 });
map.pitchTo(45, { duration: 1200 });

Внутренне используется линейная интерполяция углов с применением easing-функции.


Интерполяция времени в стилях

MapLibre GL JS поддерживает анимацию свойств стиля через transition:

map.setPaintProperty('water', 'fill-color', '#3bb2d0');

map.setPaintProperty('water', 'fill-color-transition', {
  duration: 1000,
  delay: 0
});

Параметры перехода

  • duration — длительность перехода
  • delay — задержка перед стартом
  • easing — функция интерполяции

Каждое изменение свойства превращается в временную функцию:

[ value(t) = value_{start} + (value_{end} - value_{start}) f(t)]

где t ∈ [0,1].


Функции easing и управление кривыми времени

Easing-функции определяют распределение скорости во времени.

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

t => t

Равномерное изменение без ускорения.

Кубическая плавность

t => t * t * (3 - 2 * t)

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

Кастомные кривые

t => Math.pow(t, 0.7)

Позволяют моделировать нелинейное восприятие времени.


Синхронизация анимации с внешним временем

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

Пример: движение объекта по времени

const start = performance.now();

function animate() {
  const now = performance.now();
  const t = (now - start) / 5000;

  map.setFeatureState(
    { source: 'vehicle', id: 1 },
    {
      progress: t
    }
  );

  if (t < 1) requestAnimationFrame(animate);
}

Здесь время нормализуется в диапазон [0,1], что позволяет использовать его в выражениях стиля.


Использование feature-state как временного канала

Для анимации линий и маршрутов часто применяется line-progress:

map.addLayer({
  id: 'route',
  type: 'line',
  source: 'route',
  paint: {
    'line-gradient': [
      'interpolate',
      ['linear'],
      ['line-progress'],
      0, 'blue',
      1, 'red'
    ]
  }
});

line-progress фактически становится временной координатой вдоль геометрии.


Управление кадрами через triggerRepaint

Метод triggerRepaint() принудительно инициирует перерисовку кадра.

map.triggerRepaint();

Он используется для:

  • внешних анимаций
  • синхронизации с requestAnimationFrame
  • пользовательских WebGL-слоёв

Временная модель при этом остаётся внешней, а MapLibre лишь реагирует на изменения состояния.


Пользовательские анимации через requestAnimationFrame

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

function animate(timestamp) {
  const progress = (timestamp % 2000) / 2000;

  map.setBearing(progress * 360);

  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

Здесь время задаётся как функция от системного таймстампа.


Ограничение скорости анимации и пропуск кадров

В условиях высокой нагрузки браузер может:

  • пропускать кадры
  • снижать частоту вызовов requestAnimationFrame

MapLibre компенсирует это через использование абсолютного времени (timestamp), а не количества кадров.

Это означает:

  • анимация не зависит от FPS
  • длительность остаётся стабильной
  • интерполяция всегда основана на времени, а не на шагах

Приостановка и возобновление анимации

Явного API «pause/resume» для всех типов анимаций нет, но поведение можно моделировать через контроль времени:

let paused = false;
let pauseTime = 0;

function animate(timestamp) {
  if (!paused) {
    pauseTime = timestamp;
  }

  const t = (timestamp - pauseTime) / 3000;

  map.setZoom(5 + t);

  requestAnimationFrame(animate);
}

Такой подход фиксирует временную точку остановки и компенсирует её при продолжении.


Временные ограничения длительных анимаций

При работе с flyTo и easeTo длительность может быть ограничена внутренними механизмами:

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

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


Временные характеристики WebGL-слоёв

Кастомные слои используют собственный цикл:

onAdd(map, gl) {
  this.startTime = performance.now();
}

render(gl, matrix) {
  const t = (performance.now() - this.startTime) / 1000;

  gl.uniform1f(this.u_time, t);

  this.map.triggerRepaint();
}

Здесь время становится uniform-параметром шейдера.


Нормализация времени в диапазон [0,1]

Большинство анимаций MapLibre сводятся к нормализации:

[ t = ]

Далее применяется ограничение:

[ t = clamp(t, 0, 1)]

и передача в easing-функцию.


Композиция нескольких временных анимаций

При одновременном выполнении нескольких переходов применяется независимая временная шкала:

  • камера имеет один таймер
  • слои имеют собственные transition-таймеры
  • пользовательские анимации используют performance.now

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


Детерминированность и воспроизводимость анимаций

При одинаковых входных параметрах:

  • координат
  • длительности
  • easing-функций

анимация становится детерминированной, поскольку базируется на монотонно возрастающем временном значении timestamp.

Это позволяет:

  • воспроизводить движения объектов
  • синхронизировать несколько карт
  • создавать предсказуемые визуальные переходы