requestAnimationFrame() — почему время выполнения js нельзя… — Code Ready | Frontend — TG.ME

requestAnimationFrame() — почему время выполнения js нельзя привязывать к фиксированной частоте кадров!

Браузер самостоятельно управляет процессом обновления изображения на экране. JavaScript выполняется через event loop и не синхронизирован напрямую с частотой обновления дисплея.

Поэтому анимации, построенные на фиксированных задержках, могут вести себя по-разному на разных устройствах. setTimeout() не является механизмом синхронизации с браузерным rendering pipeline:
let x = 0;

function animate() {
x += 5;

element.style.transform = `translateX(${x}px)`;

setTimeout(animate, 16);
}

animate();


Значение 16 миллисекунд не означает один кадр. Это только минимальная задержка перед следующим выполнением callback. На дисплее 60 Hz это примерно соответствует одному кадру (~16.67 ms), но на устройствах с частотой 120 Hz, 144 Hz и выше браузер не будет синхронизировать выполнение JavaScript с каждым обновлением экрана.

В результате скорость такой анимации зависит от количества вызовов JavaScript, а не от реально прошедшего времени. Для визуальных обновлений используется requestAnimationFrame():
let x = 0;

function animate() {
x += 5;

element.style.transform = `translateX(${x}px)`;

requestAnimationFrame(animate);
}

requestAnimationFrame(animate);


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

Но сама привязка к кадрам не делает анимацию корректной. Если браузер пропустит несколько кадров из-за нагрузки, изменение состояния будет зависеть от количества вызовов requestAnimationFrame(), а не от реального времени.

Для независимости от частоты кадров необходимо использовать timestamp, который передаёт requestAnimationFrame():
let startTime = null;

function animate(timestamp) {
if (startTime === null) {
startTime = timestamp;
}

const elapsed = timestamp - startTime;
const x = elapsed * 0.1;

element.style.transform = `translateX(${x}px)`;

requestAnimationFrame(animate);
}

requestAnimationFrame(animate);


Теперь положение элемента вычисляется через прошедшее время, а не через количество кадров. Такой подход сохраняет одинаковую скорость анимации независимо от того, работает устройство на 60 Hz, 120 Hz или другой частоте обновления.

При этом requestAnimationFrame() не отменяет особенности браузерного rendering pipeline. Например, чтение геометрии элемента сразу после изменения стилей может вызвать forced synchronous layout:
element.style.width = '500px';

const height = element.offsetHeight;


После изменения стиля браузеру необходимо получить актуальное значение layout. Поэтому он может выполнить синхронный перерасчёт перед возвратом значения. В больших интерфейсах такие операции внутри animation loop становятся причиной лишней нагрузки и снижения производительности.

Также необходимо управлять жизненным циклом запущенных анимаций. Если компонент удалён, а callback продолжает создавать новые кадры, приложение продолжает выполнять ненужную работу:
let frameId;
let running = true;

function animate() {
if (!running) return;

update();

frameId = requestAnimationFrame(animate);
}

frameId = requestAnimationFrame(animate);

// cleanup
running = false;
cancelAnimationFrame(frameId);


Кроме того, requestAnimationFrame() не делает любую анимацию автоматически дешёвой. Для оптимальной работы браузеру желательно избегать постоянных layout и paint операций. Свойства вроде transform и opacity чаще позволяют обновлять слой через compositor без полного перерасчёта страницы.

🔥 requestAnimationFrame() — это не просто более плавная альтернатива таймерам. Это механизм синхронизации JavaScript с браузерным циклом обновления изображения, который позволяет выполнять визуальные изменения в правильный момент, рассчитывать анимации через реальное время, учитывать разные частоты обновления экранов и избегать лишних операций при работе с интерфейсом.

📣 Code Ready | #практика
t.me/code_ready/3252Translating to English…
❤11👍10🔥6
September 1, 2026 813 25