requestAnimationFrame vs setTimeout: Cuándo usar cada uno

Al crear animaciones fluidas o programar tareas en JavaScript, elegir entre requestAnimationFrame
y setTimeout
puede impactar significativamente el rendimiento de tu aplicación. Aunque ambos métodos programan la ejecución de funciones, sirven propósitos fundamentalmente diferentes y operan con mecanismos de temporización distintos.
Puntos Clave
requestAnimationFrame
se sincroniza con la frecuencia de actualización de pantalla del navegador para actualizaciones visuales fluidassetTimeout
proporciona temporización de propósito general para tareas no visuales y operaciones en segundo plano- Usar el método incorrecto puede causar problemas de rendimiento, agotamiento de batería y mala experiencia de usuario
requestAnimationFrame
se pausa automáticamente en pestañas inactivas, mientras quesetTimeout
continúa ejecutándose
Entendiendo las Diferencias Fundamentales
setTimeout: El Temporizador de Propósito General
setTimeout
ejecuta una función después de un retraso especificado en milisegundos. Es un temporizador simple y predecible que funciona independientemente del ciclo de renderizado del navegador.
// Ejecutar después de 1 segundo
setTimeout(() => {
console.log('Ha pasado un segundo');
}, 1000);
// Con parámetros
setTimeout((message) => {
console.log(message);
}, 2000, 'Hola después de 2 segundos');
La característica clave de setTimeout
es su ejecución basada en cola de tareas. Cuando el retraso expira, el callback se une a la cola de tareas del event loop de JavaScript, compitiendo con otras tareas por tiempo de ejecución.
requestAnimationFrame: El Especialista en Animaciones
requestAnimationFrame
(rAF) se sincroniza con el ciclo de repintado del navegador, ejecutándose típicamente a 60 fotogramas por segundo en la mayoría de pantallas. Está específicamente diseñado para actualizaciones visuales.
function animate(timestamp) {
// Actualizar animación basada en timestamp
const element = document.getElementById('animated-element');
element.style.transform = `translateX(${timestamp / 10}px)`;
// Continuar animación
if (timestamp < 5000) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
Consideraciones de Rendimiento y Temporización
Integración con el Pipeline de Renderizado del Navegador
La ventaja fundamental de requestAnimationFrame
radica en su integración con el pipeline de renderizado del navegador. Mientras que setTimeout
se dispara cuando su retraso expira, los callbacks de requestAnimationFrame
se ejecutan justo antes de que el navegador calcule el layout y pinte píxeles en la pantalla.
Esta sincronización elimina problemas comunes de animación:
- Screen tearing: Artefactos visuales por actualizaciones a mitad de fotograma
- Jank: Temporización irregular de fotogramas causando movimiento entrecortado
- Renders desperdiciados: Dibujar fotogramas que nunca se muestran
Eficiencia de Recursos
requestAnimationFrame
se pausa automáticamente cuando la pestaña del navegador se vuelve inactiva, ahorrando ciclos de CPU y vida de batería. setTimeout
continúa ejecutándose en pestañas en segundo plano, aunque los navegadores pueden limitarlo a una vez por segundo después de aproximadamente un minuto.
// Bucle de animación eficiente en batería
function gameLoop(timestamp) {
updatePhysics(timestamp);
renderGraphics();
requestAnimationFrame(gameLoop);
}
// Enfoque ineficiente con setTimeout
function inefficientLoop() {
updatePhysics();
renderGraphics();
setTimeout(inefficientLoop, 16); // Intentando 60fps
}
Discover how at OpenReplay.com.
Casos de Uso Prácticos
Cuándo usar requestAnimationFrame
Usa requestAnimationFrame
para cualquier actualización visual:
- Animaciones de propiedades CSS
- Operaciones de dibujo en Canvas
- Renderizado WebGL
- Actualizaciones de posición DOM
- Indicadores de progreso durante animaciones
// Implementación de scroll suave
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);
// Función de easing para movimiento más suave
const easeInOutQuad = progress * (2 - progress);
window.scrollTo(0, startY + distance * easeInOutQuad);
if (progress < 1) {
requestAnimationFrame(scroll);
}
}
requestAnimationFrame(scroll);
}
Cuándo usar setTimeout
Elige setTimeout
para tareas no visuales:
- Llamadas API retrasadas
- Debouncing de entrada de usuario
- Operaciones de polling
- Tareas programadas en segundo plano
- Retrasos únicos
// Búsqueda con debounce
let searchTimeout;
function handleSearchInput(query) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch(query);
}, 300);
}
// Lógica de reintento con backoff exponencial
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;
});
}
Errores Comunes y Soluciones
Asunciones sobre la Frecuencia de Fotogramas
Nunca asumas una frecuencia de fotogramas fija con requestAnimationFrame
. Diferentes pantallas se actualizan a diferentes frecuencias (60Hz, 120Hz, 144Hz). Siempre usa el parámetro timestamp para animaciones basadas en tiempo:
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;
// Mover 100 píxeles por segundo independientemente de la frecuencia de fotogramas
const pixelsPerMs = 100 / 1000;
element.style.left = `${currentLeft + pixelsPerMs * deltaTime}px`;
requestAnimationFrame(animate);
}
Fugas de Memoria
Siempre almacena y limpia los IDs de animation frame cuando los componentes se desmontan:
let animationId;
function startAnimation() {
function animate() {
// Lógica de animación aquí
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
}
function stopAnimation() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
}
// Limpiar al descargar la página
window.addEventListener('beforeunload', stopAnimation);
Guía de Decisión Rápida
Caso de Uso | Mejor Opción | Razón |
---|---|---|
Animaciones fluidas | requestAnimationFrame | Se sincroniza con la actualización de pantalla |
Renderizado Canvas/WebGL | requestAnimationFrame | Previene tearing y jank |
Polling de API | setTimeout | No está vinculado a actualizaciones visuales |
Debouncing de entrada de usuario | setTimeout | Necesita control preciso de retraso |
Barras de progreso durante animación | requestAnimationFrame | Requisito de retroalimentación visual |
Procesamiento de datos en segundo plano | setTimeout | Continúa cuando la pestaña está inactiva |
Bucles de juego | requestAnimationFrame | Rendimiento óptimo y vida de batería |
Conclusión
La elección entre requestAnimationFrame
y setTimeout
no se trata de cuál es “mejor”—se trata de usar la herramienta correcta para el trabajo. Para actualizaciones visuales y animaciones, requestAnimationFrame
proporciona rendimiento superior a través de la sincronización del navegador. Para necesidades generales de temporización y tareas en segundo plano, setTimeout
ofrece la flexibilidad y predictibilidad que necesitas. Entender estas diferencias asegura que tus aplicaciones JavaScript ofrezcan experiencias de usuario fluidas mientras gestionan eficientemente los recursos del sistema.
Preguntas Frecuentes
Aunque puedes usar setTimeout con un retraso de 16.67ms para aproximar 60fps, no se sincronizará con el ciclo de actualización real del navegador. Esto lleva a fotogramas perdidos, jank y ciclos de CPU desperdiciados. requestAnimationFrame se adapta automáticamente a la frecuencia de actualización de la pantalla.
Sí, requestAnimationFrame se adapta a la frecuencia de actualización del monitor. En una pantalla de 120Hz, se dispara aproximadamente 120 veces por segundo. Siempre usa el parámetro timestamp para calcular el delta time para velocidad de animación consistente a través de diferentes pantallas.
Si tu callback excede el presupuesto de fotograma, el navegador saltará fotogramas para mantener la capacidad de respuesta. Esto causa stuttering visible. Considera optimizar tu código, usar web workers para cálculos pesados, o reducir la complejidad de la animación.
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.