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
}
Discover how at OpenReplay.com.
Практические случаи использования
Когда использовать 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/WebGL | requestAnimationFrame | Предотвращает разрывы и рывки |
Опрос API | setTimeout | Не привязан к визуальным обновлениям |
Устранение дребезга ввода | 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.