Lottie Web использует requestAnimationFrame как базовый
механизм синхронизации отрисовки с циклом обновления браузера. Это
ключевой элемент, определяющий плавность анимаций, контроль FPS и
поведение при изменении видимости вкладки.
requestAnimationFrame (rAF) запускает функцию рендера
перед следующим перерисовыванием экрана, обычно с частотой 60 кадров в
секунду, но адаптивно снижая нагрузку при необходимости. В контексте
Lottie это означает, что каждый кадр анимации вычисляется и
отрисовывается строго в ритме браузера.
Внутренний цикл анимации в Lottie строится вокруг постоянного обновления текущего времени анимации и пересчёта состояния слоёв:
В упрощённом виде цикл выглядит так:
function tick() {
const currentTime = performance.now();
updateAnimationState(currentTime);
renderFrame();
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
Каждый вызов tick синхронизирован с refresh rate
дисплея, что исключает разрывы кадров и лишние перерасчёты.
Lottie не «играет» анимацию по кадрам напрямую. Вместо этого используется временная шкала:
Формула внутреннего расчёта:
progress = (currentTime - startTime) / duration;
frame = progress * totalFrames;
Далее frame округляется или интерполируется в зависимости от режима smooth playback.
Использование rAF даёт Lottie несколько критичных преимуществ:
При этом Lottie не пытается «форсировать» FPS выше возможностей устройства. Если экран обновляется на 144 Hz, рендер будет соответствовать этому значению.
Стандартный вызов:
const anim = lottie.loadAnimation({
container: el,
renderer: 'svg',
loop: true,
autoplay: true,
path: 'data.json'
});
В этом случае Lottie самостоятельно создаёт внутренний rAF-цикл.
При кастомном управлении анимацией автоматический цикл может быть отключён:
const anim = lottie.loadAnimation({
container: el,
renderer: 'svg',
loop: false,
autoplay: false,
path: 'data.json'
});
Далее управление берётся вручную:
let start;
function loop(timestamp) {
if (!start) start = timestamp;
const elapsed = timestamp - start;
const frame = (elapsed / 1000) * 60;
anim.goToAndStop(frame, true);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
Такой подход применяется в сценариях:
Одна из типичных задач — привязка Lottie к прокрутке страницы. rAF используется как буфер между scroll-событиями и отрисовкой, чтобы избежать избыточных пересчётов.
Архитектура выглядит так:
let scrollProgress = 0;
window.addEventListener('scroll', () => {
const max = document.body.scrollHeight - window.innerHeight;
scrollProgress = window.scrollY / max;
});
function render() {
const frame = scrollProgress * anim.totalFrames;
anim.goToAndStop(frame, true);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
Такой подход разгружает scroll handler и переносит вычисления в рендер-цикл браузера.
При работе через rAF важно исключать ситуации, когда Lottie обновляется без необходимости:
Пример оптимизации:
let lastFrame = -1;
function render() {
const frame = Math.floor(scrollProgress * anim.totalFrames);
if (frame !== lastFrame) {
anim.goToAndStop(frame, true);
lastFrame = frame;
}
requestAnimationFrame(render);
}
Это снижает нагрузку на DOM и SVG-движок.
Браузеры автоматически приостанавливают или замедляют rAF:
Lottie учитывает это поведение, но при кастомных циклах требуется ручная защита:
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
anim.pause();
} else {
anim.play();
}
});
При работе с несколькими экземплярами важно избегать создания отдельных rAF-циклов для каждого:
Антипаттерн:
Оптимальный подход:
const animations = [];
function globalLoop() {
animations.forEach(anim => {
anim.update();
});
requestAnimationFrame(globalLoop);
}
requestAnimationFrame(globalLoop);
Это особенно важно при большом количестве SVG-анимаций.
При использовании Canvas или WebGL renderer Lottie всё равно опирается на rAF, но:
requestAnimationFrame остаётся точкой синхронизации вне зависимости от backend-рендера, обеспечивая единый таймлайн обновления.
Каждая из этих проблем приводит к утечкам CPU и деградации FPS.
При длинных анимациях (десятки секунд и более) важно учитывать дрейф
времени. Использование timestamp из rAF предпочтительнее
накопительного счётчика:
let start;
function loop(time) {
if (!start) start = time;
const elapsed = time - start;
const frame = (elapsed / anim.frameRate) * 0.001;
anim.goToAndStop(frame, true);
requestAnimationFrame(loop);
}
Такой подход компенсирует микрозадержки и сохраняет синхронизацию.
requestAnimationFrame выступает центральным координатором:
Lottie Web опирается на него как на базовый «таймер кадра», превращая JSON-анимацию в поток кадров, строго привязанный к циклу отрисовки браузера.