← Назад к вопросам

Что обеспечивает плавность анимации в 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 обеспечивает плавность через:

  1. Синхронизация — вызывается ровно перед перерисовкой браузером
  2. Батчинг обновлений — группирует изменения в один кадр, избегая layout thrashing
  3. Эффективность — автоматически паузит при скрытой вкладке
  4. Высокая частота — работает с Native frame rate (60fps, 120fps и выше)

Это делает анимации очень гладкими и энергоэффективными.