Back

requestAnimationFrame vs setTimeout: Cuándo usar cada uno

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 fluidas
  • setTimeout 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 que setTimeout 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
}

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 UsoMejor OpciónRazón
Animaciones fluidasrequestAnimationFrameSe sincroniza con la actualización de pantalla
Renderizado Canvas/WebGLrequestAnimationFramePreviene tearing y jank
Polling de APIsetTimeoutNo está vinculado a actualizaciones visuales
Debouncing de entrada de usuariosetTimeoutNecesita control preciso de retraso
Barras de progreso durante animaciónrequestAnimationFrameRequisito de retroalimentación visual
Procesamiento de datos en segundo planosetTimeoutContinúa cuando la pestaña está inactiva
Bucles de juegorequestAnimationFrameRendimiento ó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.

OpenReplay