Back

requestAnimationFrame vs setTimeout: Когда использовать каждый из них

requestAnimationFrame vs setTimeout: Когда использовать каждый из них

При создании плавных анимаций или планировании задач в JavaScript выбор между requestAnimationFrame и setTimeout может существенно повлиять на производительность вашего приложения. Хотя оба метода планируют выполнение функций, они служат принципиально разным целям и работают на основе различных механизмов синхронизации.

Ключевые выводы

  • requestAnimationFrame синхронизируется с частотой обновления дисплея браузера для плавных визуальных обновлений
  • setTimeout обеспечивает универсальную синхронизацию для невизуальных задач и фоновых операций
  • Использование неправильного метода может вызвать проблемы с производительностью, разрядку батареи и плохой пользовательский опыт
  • requestAnimationFrame автоматически приостанавливается в неактивных вкладках, в то время как setTimeout продолжает работать

Понимание основных различий

setTimeout: Универсальный таймер

setTimeout выполняет функцию после указанной задержки в миллисекундах. Это простой, предсказуемый таймер, который работает независимо от цикла рендеринга браузера.

// Выполнить через 1 секунду
setTimeout(() => {
  console.log('Прошла одна секунда');
}, 1000);

// С параметрами
setTimeout((message) => {
  console.log(message);
}, 2000, 'Привет через 2 секунды');

Ключевая характеристика setTimeout — это выполнение на основе очереди задач. Когда задержка истекает, колбэк присоединяется к очереди задач цикла событий JavaScript, конкурируя с другими задачами за время выполнения.

requestAnimationFrame: Специалист по анимации

requestAnimationFrame (rAF) синхронизируется с циклом перерисовки браузера, обычно работая на частоте 60 кадров в секунду на большинстве дисплеев. Он специально разработан для визуальных обновлений.

function animate(timestamp) {
  // Обновить анимацию на основе временной метки
  const element = document.getElementById('animated-element');
  element.style.transform = `translateX(${timestamp / 10}px)`;
  
  // Продолжить анимацию
  if (timestamp < 5000) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

Соображения производительности и синхронизации

Интеграция с конвейером рендеринга браузера

Фундаментальное преимущество requestAnimationFrame заключается в его интеграции с конвейером рендеринга браузера. В то время как setTimeout срабатывает всякий раз, когда истекает его задержка, колбэки requestAnimationFrame выполняются непосредственно перед тем, как браузер вычисляет макет и отрисовывает пиксели на экране.

Эта синхронизация устраняет распространенные проблемы анимации:

  • Разрыв экрана: Визуальные артефакты от обновлений в середине кадра
  • Рывки: Нерегулярная синхронизация кадров, вызывающая прерывистое движение
  • Потраченные рендеры: Отрисовка кадров, которые никогда не отображаются

Эффективность ресурсов

requestAnimationFrame автоматически приостанавливается, когда вкладка браузера становится неактивной, экономя циклы процессора и заряд батареи. setTimeout продолжает выполняться в фоновых вкладках, хотя браузеры могут ограничивать его до одного раза в секунду примерно через минуту.

// Энергоэффективный цикл анимации
function gameLoop(timestamp) {
  updatePhysics(timestamp);
  renderGraphics();
  requestAnimationFrame(gameLoop);
}

// Неэффективный подход с setTimeout
function inefficientLoop() {
  updatePhysics();
  renderGraphics();
  setTimeout(inefficientLoop, 16); // Попытка достичь 60fps
}

Практические случаи использования

Когда использовать requestAnimationFrame

Используйте requestAnimationFrame для любых визуальных обновлений:

  • Анимации CSS-свойств
  • Операции рисования на canvas
  • Рендеринг WebGL
  • Обновления позиций DOM-элементов
  • Индикаторы прогресса во время анимаций
// Реализация плавной прокрутки
function smoothScrollTo(targetY, duration) {
  const startY = window.scrollY;
  const distance = targetY - startY;
  const startTime = performance.now();
  
  function scroll(currentTime) {
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1);
    
    // Функция сглаживания для более плавного движения
    const easeInOutQuad = progress * (2 - progress);
    
    window.scrollTo(0, startY + distance * easeInOutQuad);
    
    if (progress < 1) {
      requestAnimationFrame(scroll);
    }
  }
  
  requestAnimationFrame(scroll);
}

Когда использовать setTimeout

Выбирайте setTimeout для невизуальных задач:

  • Отложенные API-вызовы
  • Устранение дребезга пользовательского ввода
  • Операции опроса
  • Запланированные фоновые задачи
  • Одноразовые задержки
// Поиск с устранением дребезга
let searchTimeout;
function handleSearchInput(query) {
  clearTimeout(searchTimeout);
  searchTimeout = setTimeout(() => {
    performSearch(query);
  }, 300);
}

// Логика повторных попыток с экспоненциальной задержкой
function fetchWithRetry(url, attempts = 3, delay = 1000) {
  return fetch(url).catch(error => {
    if (attempts > 1) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(fetchWithRetry(url, attempts - 1, delay * 2));
        }, delay);
      });
    }
    throw error;
  });
}

Распространенные ошибки и решения

Предположения о частоте кадров

Никогда не предполагайте фиксированную частоту кадров с requestAnimationFrame. Различные дисплеи обновляются с разной частотой (60Гц, 120Гц, 144Гц). Всегда используйте параметр временной метки для анимаций, основанных на времени:

let lastTime = 0;
function animate(currentTime) {
  const deltaTime = currentTime - lastTime;
  lastTime = currentTime;
  
  const element = document.getElementById('moving-element');
  const currentLeft = parseFloat(element.style.left) || 0;
  
  // Перемещать на 100 пикселей в секунду независимо от частоты кадров
  const pixelsPerMs = 100 / 1000;
  element.style.left = `${currentLeft + pixelsPerMs * deltaTime}px`;
  
  requestAnimationFrame(animate);
}

Утечки памяти

Всегда сохраняйте и очищайте идентификаторы кадров анимации при размонтировании компонентов:

let animationId;

function startAnimation() {
  function animate() {
    // Логика анимации здесь
    animationId = requestAnimationFrame(animate);
  }
  animationId = requestAnimationFrame(animate);
}

function stopAnimation() {
  if (animationId) {
    cancelAnimationFrame(animationId);
    animationId = null;
  }
}

// Очистка при выгрузке страницы
window.addEventListener('beforeunload', stopAnimation);

Краткое руководство по принятию решений

Случай использованияЛучший выборПричина
Плавные анимацииrequestAnimationFrameСинхронизация с обновлением дисплея
Рендеринг Canvas/WebGLrequestAnimationFrameПредотвращает разрывы и рывки
Опрос APIsetTimeoutНе привязан к визуальным обновлениям
Устранение дребезга вводаsetTimeoutНужен точный контроль задержки
Индикаторы прогресса во время анимацииrequestAnimationFrameТребование визуальной обратной связи
Фоновая обработка данныхsetTimeoutПродолжается при неактивной вкладке
Игровые циклыrequestAnimationFrameОптимальная производительность и время работы батареи

Заключение

Выбор между requestAnimationFrame и setTimeout — это не вопрос о том, что “лучше”, а о том, чтобы использовать правильный инструмент для работы. Для визуальных обновлений и анимаций requestAnimationFrame обеспечивает превосходную производительность благодаря синхронизации с браузером. Для общих потребностей в синхронизации и фоновых задач setTimeout предлагает необходимую гибкость и предсказуемость. Понимание этих различий гарантирует, что ваши JavaScript-приложения обеспечат плавный пользовательский опыт при эффективном управлении системными ресурсами.

Часто задаваемые вопросы

Хотя вы можете использовать setTimeout с задержкой 16.67мс для приближения к 60fps, он не будет синхронизироваться с фактическим циклом обновления браузера. Это приводит к пропущенным кадрам, рывкам и потраченным циклам процессора. requestAnimationFrame автоматически адаптируется к частоте обновления дисплея.

Да, requestAnimationFrame адаптируется к частоте обновления монитора. На дисплее 120Гц он срабатывает примерно 120 раз в секунду. Всегда используйте параметр временной метки для вычисления дельта-времени для обеспечения постоянной скорости анимации на различных дисплеях.

Если ваш колбэк превышает бюджет кадра, браузер будет пропускать кадры для поддержания отзывчивости. Это вызывает видимые рывки. Рассмотрите оптимизацию вашего кода, использование web workers для тяжелых вычислений или снижение сложности анимации.

Gain control over your UX

See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay