← Назад к вопросам
Что обеспечивает плавность анимации в Request Animation Frame?
2.0 Middle🔥 71 комментариев
#Браузер и сетевые технологии#Оптимизация и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что обеспечивает плавность анимации в Request Animation Frame?
Request Animation Frame (RAF) — это встроенная функция браузера, которая обеспечивает синхронизацию анимаций с частотой обновления экрана. Давайте разберёмся, что делает анимации гладкими.
Как работает requestAnimationFrame
requestAnimationFrame синхронизирует выполнение кода с частотой обновления монитора (обычно 60 кадров в секунду):
// Наивный подход — использование setTimeout
const element = document.getElementById("box");
let position = 0;
function animateWithTimeout() {
position += 10;
element.style.left = position + "px";
if (position < 500) {
setTimeout(animateWithTimeout, 16); // 16ms ≈ 60fps
}
}
// Проблемы:
// 1. setTimeout не синхронизирован с браузером
// 2. Может быть задержка или пропуск кадров
// 3. Тратит ресурсы впустую, если вкладка не видна
// Правильный подход — requestAnimationFrame
let position = 0;
function animate() {
position += 10;
element.style.left = position + "px";
if (position < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
Три ключевых фактора плавности RAF
1. Синхронизация с refresh rate монитора
РАФ вызывает callback ровно перед каждой перерисовкой экрана:
// На мониторе 60Hz:
// ┌─────────────┬─────────────┬─────────────┐
// │ Frame 0 │ Frame 1 │ Frame 2 │
// │ (0-16.67ms) │(16.67-33ms) │(33-50ms) │
// ├─────────────┼─────────────┼─────────────┤
// │ RAF exec │ RAF exec │ RAF exec │
// └─────────────┴─────────────┴─────────────┘
// ↓ ↓ ↓
// Paint Paint Paint
let lastTime = 0;
function animateWithDelta(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// deltaTime будет примерно 16.67ms на 60Hz мониторе
console.log(`Delta: ${deltaTime}ms`);
// Используем deltaTime для независимого от FPS движения
const speed = 300; // пиксели в секунду
const distance = (speed * deltaTime) / 1000;
position += distance;
element.style.left = position + "px";
if (position < 500) {
requestAnimationFrame(animateWithDelta);
}
}
requestAnimationFrame(animateWithDelta);
2. Избежание layout thrashing
RAF помогает избежать множественных перерисовок за один кадр:
// ❌ Плохо — вызывает переразметку за каждое изменение
function animateMultipleElements() {
box1.style.left = "100px"; // ← перерисовка
console.log(box1.offsetLeft); // ← запрос, вызовет перерасчёт
box2.style.top = "50px"; // ← перерисовка
console.log(box2.offsetTop); // ← запрос, вызовет перерасчёт
}
// ✅ Хорошо — батчим все изменения в одном кадре
function animateMultipleElements() {
requestAnimationFrame(() => {
box1.style.left = "100px";
box2.style.top = "50px";
// Браузер выполнит одну перерисовку для обоих
});
// Запросы читаем отдельно
requestAnimationFrame(() => {
console.log(box1.offsetLeft);
console.log(box2.offsetTop);
});
}
3. Автоматическое управление производительностью
РАФ автоматически приостанавливается, когда вкладка не видна:
// Если пользователь переключился на другую вкладку,
// RAF не будет вызываться
// Это экономит батарею и ресурсы
let animationId;
function startAnimation() {
function animate() {
// Код анимации
position += 10;
element.style.left = position + "px";
if (position < 500) {
animationId = requestAnimationFrame(animate);
}
}
animationId = requestAnimationFrame(animate);
}
function stopAnimation() {
cancelAnimationFrame(animationId);
}
Практический пример — плавная анимация
class Animator {
constructor(element, duration = 1000) {
this.element = element;
this.duration = duration;
this.startTime = null;
this.animationId = null;
}
// Easing функция для натурального движения
easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3);
}
animate(from, to) {
return new Promise((resolve) => {
this.startTime = null;
const animateFrame = (currentTime) => {
// Инициализируем время в первом кадре
if (!this.startTime) {
this.startTime = currentTime;
}
const elapsed = currentTime - this.startTime;
const progress = Math.min(elapsed / this.duration, 1);
const eased = this.easeOutCubic(progress);
// Интерполяция значений
const value = from + (to - from) * eased;
this.element.style.left = value + "px";
if (progress < 1) {
this.animationId = requestAnimationFrame(animateFrame);
} else {
resolve();
}
};
this.animationId = requestAnimationFrame(animateFrame);
});
}
}
// Использование
const animator = new Animator(document.getElementById("box"), 1000);
await animator.animate(0, 500);
Сравнение подходов
// setTimeout — нет гарантии синхронизации
setTimeout(() => { /* анимация */ }, 16);
// setInterval — может выполниться дважды подряд или пропуститься
setInterval(() => { /* анимация */ }, 16);
// requestAnimationFrame ✅ — идеально синхронизирован
requestAnimationFrame(() => { /* анимация */ });
// CSS animations / transitions — ещё лучше
element.style.transition = "left 1s ease-out";
element.style.left = "500px";
// Web Animations API — полный контроль
element.animate(
[{left: "0px"}, {left: "500px"}],
{duration: 1000, easing: "ease-out"}
);
Итоги
RequestAnimationFrame обеспечивает плавность через:
- Синхронизация — вызывается ровно перед перерисовкой браузером
- Батчинг обновлений — группирует изменения в один кадр, избегая layout thrashing
- Эффективность — автоматически паузит при скрытой вкладке
- Высокая частота — работает с Native frame rate (60fps, 120fps и выше)
Это делает анимации очень гладкими и энергоэффективными.