Кадровая анимация в браузере опирается на принцип дискретного
обновления состояния сцены. В отличие от непрерывных физических
процессов, визуальная система разбивает движение на последовательность
кадров, синхронизированных с частотой обновления экрана. В JavaScript
основной механизм такого синхронизированного обновления —
requestAnimationFrame, который предоставляет время
выполнения каждого кадра и гарантирует оптимальную частоту
отрисовки.
В Popmotion управление временем между кадрами строится вокруг идеи стабильного расчёта прогресса анимации независимо от того, насколько нерегулярно приходят кадры. Это критично для плавности, предсказуемости и корректного поведения как простых tween-анимаций, так и физически основанных систем.
Каждый кадр в браузере сопровождается меткой времени, передаваемой в
callback requestAnimationFrame. Это значение выражается в
миллисекундах и отражает момент, когда браузер начал обработку
кадра.
Popmotion использует этот timestamp как базовую точку отсчёта. Вместо того чтобы полагаться на фиксированные шаги времени, система вычисляет разницу между текущим и предыдущим кадром:
const delta = currentTime - previousTime;
Эта разница и становится фундаментом всей логики обновления состояния.
Если бы система использовала фиксированный шаг, например 16.67 мс (60 FPS), любые колебания частоты кадров приводили бы к рассинхронизации анимации. Реальные устройства редко обеспечивают идеально стабильный FPS, поэтому Popmotion опирается на фактическое время между кадрами.
В реальной среде интервал между кадрами может существенно меняться:
Если анимация рассчитывается только по количеству кадров, без учёта времени, движение становится нелинейным: объекты ускоряются или замедляются в зависимости от производительности системы.
Popmotion устраняет эту проблему, переводя всю модель анимации в пространство времени, а не кадров.
Одним из ключевых этапов обработки является нормализация delta time. В Popmotion часто используется масштабирование времени относительно базовой частоты 60 FPS:
[ scaledDelta = ]
Это позволяет интерпретировать каждое обновление как «эквивалент» стандартного кадра, даже если реальный интервал отличается.
Такой подход делает поведение анимации стабильным на разных устройствах. Например, если кадр занял 33 мс, система воспринимает это как два стандартных шага.
Однако прямое масштабирование применяется не всегда. В физически точных анимациях чаще используется реальное время без нормализации, чтобы сохранить корректность интеграции движения.
Если кадры задерживаются, возникает проблема «накопленного времени». Например, при паузе в 200 мс следующий кадр должен обработать сразу большой интервал.
Простейшая обработка привела бы к резкому скачку анимации. Popmotion решает это через ограничение максимального delta:
const maxDelta = 40; // ограничение ~25 FPS
const cappedDelta = Math.min(delta, maxDelta);
Такой приём предотвращает «телепортацию» анимации после лагов и делает движение более контролируемым.
В более сложных системах применяется разбиение большого интервала на несколько шагов:
while (remainingTime > step) {
update(step);
remainingTime -= step;
}
Это позволяет сохранить стабильность даже при сильных просадках производительности.
Popmotion не обновляет значения напрямую на каждом кадре без промежуточных вычислений. Вместо этого используется интерполяция — вычисление промежуточного состояния между начальным и конечным значением на основе прогресса времени.
Прогресс обычно выражается в диапазоне от 0 до 1:
[ progress = ]
Далее применяется функция сглаживания (easing), которая преобразует линейный прогресс в нелинейный:
Таким образом, распределение времени между кадрами влияет не только на скорость, но и на форму кривой движения.
В spring-анимациях Popmotion время напрямую участвует в численном интегрировании уравнений движения. Здесь кадр не просто обновляет значение, а является шагом симуляции.
Базовая модель включает:
Каждый кадр вычисляет новое состояние системы:
velocity += acceleration * delta;
position += velocity * delta;
Если delta нестабилен, система становится либо чрезмерно «энергичной», либо затухающей. Поэтому корректная обработка времени между кадрами критична для физической достоверности.
Внутренне Popmotion разделяет два процесса:
Такое разделение позволяет компенсировать задержки рендера. Даже если визуальная часть не успевает, вычисления продолжают опираться на корректное время.
Это особенно важно при сложных сценах, где рендер может занимать значительную часть кадра.
Jank — это пропуск или задержка кадра. В таких ситуациях важно не пытаться «додумать» промежуточные состояния, а корректно пересчитать прогресс.
Popmotion применяет стратегию временного позиционирования:
Это предотвращает накопление ошибок и дрейф анимации.
При последовательных анимациях (sequence) распределение времени становится ещё более сложным. Каждый элемент цепочки имеет собственный диапазон времени, но все они зависят от общего таймера.
Popmotion использует глобальный «мастер-таймер», от которого каждый сегмент берёт свою часть:
На каждом кадре определяется, какой сегмент активен, и его локальный прогресс пересчитывается относительно глобального времени.
Со временем даже высокоточные таймеры могут накапливать погрешности. Popmotion минимизирует дрейф за счёт отказа от инкрементального накопления времени.
Вместо:
time += delta;
используется:
time = currentTime - startTime;
Это гарантирует, что каждый кадр опирается на абсолютное время, а не на цепочку вычислений.
requestAnimationFrame синхронизирует выполнение кода с
циклом рендеринга браузера. Popmotion использует это для автоматического
выравнивания анимации с:
Это позволяет минимизировать визуальные артефакты и обеспечивает совпадение логического времени анимации с физическим временем отрисовки.
Современные дисплеи могут иметь 60, 120 и более герц. Это означает, что интервал между кадрами меняется динамически.
Popmotion не привязывается к фиксированному FPS. Вместо этого:
Это обеспечивает одинаковое поведение анимации на разных устройствах без ручной адаптации.
При высоком FPS возникает проблема чрезмерного количества обновлений логики. Чтобы избежать лишней нагрузки, Popmotion может объединять близкие по времени кадры и пропускать промежуточные вычисления, если они не влияют на итоговое состояние.
Это особенно важно для сложных анимаций с большим количеством объектов, где каждый update включает расчёт трансформаций и интерполяций.
JavaScript не гарантирует точную периодичность выполнения
requestAnimationFrame. Поэтому Popmotion рассматривает
каждое обновление как событие с произвольным интервалом.
Система проектируется так, чтобы:
Это достигается через отказ от зависимости от предыдущего состояния в пользу абсолютного времени.
Внутри Popmotion время между кадрами формирует не просто интервал, а основную ось всей анимационной системы. Каждый кадр рассматривается как функция времени:
[ state = f(time)]
а не как последовательность приращений:
[ state_{n+1} = state_n + ]
Такое представление позволяет сохранять стабильность, предсказуемость и физическую корректность поведения анимаций независимо от условий выполнения.