Синхронизация анимаций

Синхронизация анимаций в Lottie Web опирается на внутреннюю временную модель, в которой ключевыми сущностями выступают кадры (frames), частота кадров (fps) и длительность композиции. Любая анимация интерпретируется как дискретная последовательность кадров, привязанная к шкале времени.

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

  • frameRate — количество кадров в секунду
  • totalFrames — общее число кадров композиции
  • duration — длительность в секундах, вычисляемая как totalFrames / frameRate

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


Базовые механизмы управления временной шкалой

Синхронизация нескольких Lottie-анимаций начинается с унификации управления их состоянием.

Управление воспроизведением

Ключевые методы:

  • play() — запуск с текущего кадра
  • pause() — остановка
  • stop() — сброс к начальному состоянию
  • goToAndStop(frame, isFrame) — переход на кадр с остановкой
  • goToAndPlay(frame, isFrame) — переход и запуск
  • setSpeed(value) — изменение скорости воспроизведения

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


Синхронизация через общий временной источник

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

Нормализованный прогресс

Вместо работы с кадрами напрямую используется нормализованный коэффициент:

progress = currentTime / duration

Каждая анимация получает одинаковое значение прогресса и преобразует его в кадры:

frame = progress * totalFrames

Пример синхронного обновления

const animations = [anim1, anim2, anim3];
const duration = 4000;

const start = performance.now();

function tick(now) {
  const elapsed = now - start;
  const progress = (elapsed % duration) / duration;

  animations.forEach(anim => {
    const frame = progress * anim.totalFrames;
    anim.goToAndStop(frame, true);
  });

  requestAnimationFrame(tick);
}

requestAnimationFrame(tick);

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


Синхронизация через enterFrame

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

Принцип работы

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

master.addEventListener('enterFrame', (e) => {
  const frame = e.currentTime;

  slave1.goToAndStop(frame, true);
  slave2.goToAndStop(frame, true);
});

Такой подход эффективен при одинаковых или близких композициях.


Архитектура master-slave

Модель синхронизации часто строится на разделении ролей:

  • master-анимация — источник времени
  • slave-анимации — следуют за master

Особенности модели

  • Упрощение управления
  • Единая точка контроля
  • Возможность каскадной синхронизации

Ограничения

  • Зависимость от FPS master-композиции
  • Возможный дрейф при сложной нагрузке рендера

Синхронизация скоростей воспроизведения

Метод setSpeed позволяет унифицировать темп анимаций без ручного управления кадрами.

animations.forEach(anim => {
  anim.setSpeed(1);
});

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


Сегментная синхронизация

Lottie поддерживает воспроизведение сегментов анимации:

anim.playSegments([10, 60], true);

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

  • одинаковые стартовые и конечные кадры
  • идентичная интерпретация длины сегмента

Использование в синхронных сценах

Сегменты применяются для:

  • синхронных переходов
  • анимации состояний интерфейса
  • параллельных эффектов

Выравнивание через requestAnimationFrame

Для высокоточной синхронизации используется внешний цикл рендеринга.

Принцип

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

function loop() {
  const t = performance.now();

  const frame = (t / 16.666) % anim.totalFrames;

  anim1.goToAndStop(frame, true);
  anim2.goToAndStop(frame, true);

  requestAnimationFrame(loop);
}

Преимущество

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

Дрейф и накопление ошибок

При длительной работе анимаций возникает проблема временного дрейфа:

  • различие в FPS рендерера и логики
  • задержки вызовов requestAnimationFrame
  • различия в вычислении frame time

Причины рассинхронизации

  • плавающая точка при вычислении progress
  • независимые внутренние таймеры Lottie
  • пропуски кадров при высокой нагрузке

Коррекция временного отклонения

Для устранения дрейфа используется периодическая коррекция.

Ресинхронизация по эталону

const masterFrame = master.currentFrame;

slaves.forEach(anim => {
  anim.goToAndStop(masterFrame, true);
});

Периодическая привязка

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

  • каждые 200–500 мс
  • при смене состояния интерфейса
  • при возврате в активное окно

Синхронизация через IntersectionObserver

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

Поведение

Анимации могут:

  • ставиться на паузу вне экрана
  • синхронизироваться при появлении
  • пересчитывать прогресс при возврате
observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      anim.play();
    } else {
      anim.pause();
    }
  });
});

Связка с аудио-временем

При синхронизации с аудиопотоком используется Web Audio API как источник абсолютного времени.

Принцип привязки

const audioTime = audioContext.currentTime;

const frame = audioTime * fps;
anim.goToAndStop(frame, true);

Особенность

Аудио обеспечивает более стабильную временную шкалу, чем requestAnimationFrame.


Синхронизация разных композиций

При работе с различными Lottie-композициями возникает задача нормализации:

  • разные totalFrames
  • разные fps
  • разные длительности

Нормализация через прогресс

Все анимации приводятся к единому диапазону:

progress ∈ [0, 1]

И затем масштабируются индивидуально:

frame_i = progress * totalFrames_i

Каскадная синхронизация

В сложных сценах применяется иерархия:

  • глобальный таймер
  • группы анимаций
  • локальные контроллеры

Каждый уровень может модифицировать прогресс:

  • задержка (offset)
  • ускорение (scale)
  • инверсия направления

Практики предотвращения рассинхронизации

Используются следующие подходы:

  • отказ от автономного play() в пользу ручного управления кадрами
  • унификация fps всех композиций
  • минимизация вызовов внутренних таймеров Lottie
  • периодическое выравнивание по master-frame
  • использование единого requestAnimationFrame цикла

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

При пропуске frames система может:

  • перескочить несколько кадров сразу
  • визуально «дёрнуть» синхронизацию
  • уйти в рассогласование при разных скоростях отрисовки

Решение

Используется пересчёт на основе абсолютного времени, а не инкрементального:

frame = (performance.now() - start) * fps / 1000

Итоговая модель синхронизации

В устойчивой архитектуре синхронизация Lottie-анимаций строится на следующих принципах:

  • единый источник времени
  • управление через нормализованный прогресс
  • ручной контроль кадров через goToAndStop
  • периодическая коррекция отклонений
  • отказ от независимого воспроизведения
  • использование requestAnimationFrame как базового цикла обновления