El secreto de las animaciones en JavaScript (requestAnimationFrame())

- 👤 Andrés Cruz

🇺🇸 In english

El secreto de las animaciones en JavaScript (requestAnimationFrame())

En esta entrada veremos como crear animaciones con solo JavaScript lo cual puede convertirse en un gran recurso al momento de realizar nuestros desarrollos; crear animaciones con JavaScript es más simple de lo que parece y para ponerlo más interesante esta API está integrada con la API de Canvas lo que aumenta su versatilidad y sencillez en su uso.

Crear animaciones en JavaScript puede parecer algo complejo al principio, pero en la práctica es mucho más sencillo de lo que muchos imaginan. En mi caso, durante bastante tiempo pensé que para lograr animaciones fluidas era obligatorio recurrir a librerías externas o a CSS, hasta que empecé a trabajar directamente con requestAnimationFrame() y, sobre todo, a combinarlo con Canvas.

En esta entrada veremos cómo crear animaciones usando solo JavaScript, por qué requestAnimationFrame() es la forma correcta de hacerlo y cómo aprovecharlo en ejemplos prácticos que realmente funcionan bien en el navegador.

Recordando las animaciones “clásicas” en JavaScript

Si llevas algo de tiempo desarrollando en JavaScript, seguramente setTimeout() y setInterval() te suenan bastante. Durante años fueron la solución habitual cuando queríamos ejecutar código de forma periódica o simular una animación.

Un ejemplo típico era algo como esto:

function draw() {
   setTimeout(pintar, 100);
   // código para dibujar
}
pintar();

El problema es que estas funciones no están pensadas para animaciones. En más de una ocasión me encontré con navegadores congelados, animaciones poco fluidas o un consumo de CPU exagerado simplemente por estar ejecutando lógica de dibujo sin control real del repintado de pantalla.

El mayor inconveniente es que:

  • No están sincronizadas con el refresco del navegador.
  • Siguen ejecutándose incluso cuando la pestaña está en segundo plano.
  • No permiten optimización automática por parte del navegador.

Animar con librerías: cómodo, pero no siempre necesario

Otra opción bastante común es usar librerías o plugins de animación. Y no hay nada de malo en ello: muchas automatizan gran parte del trabajo y ofrecen resultados excelentes.

Sin embargo, con el tiempo descubrí que entender la API nativa te da un control mucho mayor. Para animaciones personalizadas, físicas simples, Canvas o lógica dependiente del estado, conocer requestAnimationFrame() marca una diferencia enorme.

Animando un Canvas con Window.requestAnimationFrame()

La función que hablábamos en un inicio que permite animar objetos en el Canvas se llama Window.requestAnimationFrame() la cual toma como parámetro a la función que se encarga de pintar (y repintar) -en nuestro caso- el Canvas; por ejemplo, siguiendo el mismo esquema empleado en las "clásicas" animaciones tenemos que:

function draw() {
    requestAnimationFrame(draw);
    // Drawing code goes here
}
draw();

Esta API le indica al navegador que:

  • Queremos realizar una animación.
  • Debe ejecutar una función justo antes del siguiente repintado.
  • Sincronice la ejecución con la frecuencia de refresco (normalmente 60 FPS).

Con la función requestAnimationFrame() se obtienen transiciones o cambios suaves a través de una API que se encuentra optimizado para tal fin.

Aquí ocurre algo importante: requestAnimationFrame() no es un bucle automático. Cada llamada programa un solo frame, por lo que debemos llamarlo de nuevo dentro de la propia función para mantener la animación activa.

Cuando lo probé por primera vez, lo que más me sorprendió fue la suavidad inmediata frente a setInterval(), incluso con código sencillo.

¿Por qué requestAnimationFrame es mejor que setInterval?

Las ventajas son claras:

  • ✔️ Animaciones más fluidas.
  • ✔️ Menor consumo de CPU y batería.
  • ✔️ Se pausa automáticamente en pestañas inactivas.
  • ✔️ El navegador decide el mejor momento para dibujar.

En lugar de forzar un intervalo fijo, dejamos que el navegador optimice el repintado y agrupe todas las animaciones en un solo ciclo.

Animando un cuadrado en Canvas con requestAnimationFrame()

Una de las combinaciones más potentes es Canvas + requestAnimationFrame(). Aquí es donde realmente se nota la versatilidad de esta API.

En este pequeño experimento veremos cómo animar un simple cuadrado; en específico haremos que el cuadrado se mueva de una diagonal a la otra; el siguiente código JavaScript hace lo especificado anteriormente:

 var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// dimensiones del canvas
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;

// posiciones iniciales
var x = width;
var y = 0;

// velocidad
var vx = 3;
var vy = 2;

function animar() {
    // limpiamos el canvas
    ctx.clearRect(0, 0, width, height);

    // dibujamos un cuadrado de 10x10
    ctx.fillRect(x, y, 10, 10);

    // actualizamos la posición
    x -= vx;
    y += vy;

    // solicitamos el siguiente frame
    requestAnimationFrame(animar);

    // validamos límites
    if (y > height || x < 0) {
        x = width;
        y = 0;
    }
}

animar();

Analicemos el código presentado anteriormente:

  1. Primero inicializamos los elementos básicos para poder trabajar con Canvas.
  2. var canvas = document.getElementById('canvas'); 
    var ctx = canvas.getContext('2d'); // obtenemos las dimensiones del canvas 
    var width = canvas.width = window.innerWidth; 
    var height = canvas.height = window.innerHeight;
  3. Definimos la posición por defecto (o las coordenadas X y Y) en donde es dibujado inicialmente la figura; además es definido un par de variables para especificar la velocidad de la animación. // posiciones var x = width; var y = 0; // velocidad var vx = 3; var vy = 2;
  4. Una de las tareas que realiza la función animar() es la de actualizar las variables x y y con el fin de variar la posición del cuadrado.  // actualizamos la posicion  x -= vx;  y += vy;

Esta sección en fundamental debido a que es donde alteramos la posición del cuadrado lo cual es necesario para realizar la animación.

  1. Invocamos el método requestAnimationFrame(animar); recursivamente especificando como parámetro el nombre de la función (animar()).

Finalmente, la animación con JavaScript:

Ver ejemplo Descargar

Con solo quitar el llamado a la función ctx.clearRect() y de esta forma no limpiarel Canvas obtenemos el siguiente resultado:

Refresca para ver la animación.

Ver ejemplo Descargar

Y si alteramos los valores de la "velocidad" para cambiarla entre positiva y negativa y validando que no se salga de rango:

	if(vx > 0){
		  if (x > width) 
			vx *=-1;
		  if (y > height) {
			vx = -8;
		 	vy = 2;
			x=width;
			y=0;
		  }
	}else
		if (x < 0) 
			vx *=-1;
	}

Obtenemos:

Ver ejemplo Descargar

¿Qué pasa si no limpiamos el Canvas?

Un detalle interesante es que, si eliminamos esta línea:

ctx.clearRect(0, 0, width, height);

El canvas no se limpia, y el cuadrado deja un rastro visual. Con solo ese pequeño cambio obtenemos un efecto completamente distinto, algo que descubrí probando y experimentando con el código.

Este tipo de pruebas ayudan mucho a entender cómo funciona realmente el renderizado.

Valor devuelto por Window.requestAnimationFrame()

Al invocar la función requestAnimationFrame() la misma devuelve un código distinto de cero que debe ser empleado en caso de querer cancelar la animación:

id = requestAnimationFrame(callback); 
cancelAnimationFrame(id);

Cambiando dirección y velocidad

Si alteramos los valores de la velocidad y validamos los límites, podemos hacer que el objeto rebote o cambie de dirección:

if (vx > 0) {
   if (x > width) vx *= -1;
   if (y > height) {
       vx = -8;
       vy = 2;
       x = width;
       y = 0;
   }
} else {
   if (x < 0) vx *= -1;
}

Aquí es donde requestAnimationFrame() demuestra su utilidad para animaciones dinámicas, dependientes del estado y no solo del tiempo.

Uso del timestamp: animaciones independientes del hardware

Un punto importante que muchas veces se pasa por alto es el uso del timestamp que recibe el callback:

function animar(timestamp) {
   // usar timestamp para calcular desplazamientos
}

Utilizar este valor (o performance.now()) evita que la animación vaya más rápido en pantallas con tasas de refresco altas (120 Hz, 144 Hz). Es una buena práctica especialmente en animaciones más complejas o físicas.

Cancelar una animación con cancelAnimationFrame()

requestAnimationFrame() devuelve un identificador que podemos usar para detener la animación:

let id = requestAnimationFrame(animar);
cancelAnimationFrame(id);

Esto resulta muy útil cuando:

  • Queremos pausar la animación.
  • El usuario cambia de vista.
  • La animación ya cumplió su objetivo.

¿Cuándo usar requestAnimationFrame y cuándo no?

En general:

  • ✅ Canvas, animaciones físicas, lógica compleja → requestAnimationFrame
  • ✅ Animaciones simples de UI → CSS
  • ❌ Evitar setInterval para animaciones

Siempre que la animación depende de datos, estados o cálculos, requestAnimationFrame() termina siendo la mejor opción.

FAQs sobre requestAnimationFrame()

  • ¿requestAnimationFrame funciona a 60 FPS?
    • Normalmente sí, pero se adapta a la tasa de refresco del dispositivo.
  • ¿Se pausa en segundo plano?
    • Sí, el navegador reduce o pausa la ejecución en pestañas inactivas.
  • ¿Puedo usarlo sin Canvas?
    • Por supuesto. Funciona tanto con DOM como con Canvas.
  • ¿Es mejor que setInterval?
    • Para animaciones, claramente sí.

Conclusión

requestAnimationFrame() es una API esencial para crear animaciones fluidas, eficientes y modernas en JavaScript. Entender cómo funciona, especialmente junto a Canvas, abre muchas posibilidades sin necesidad de librerías externas.

Una vez interiorizas que la animación consiste en actualizar estado + dibujar + repetir, todo encaja de forma natural y el código se vuelve mucho más predecible y mantenible.

Acepto recibir anuncios de interes sobre este Blog.

Veremos cómo realizar algunas simples animaciones en JavaScript mediante requestAnimationFrame() cuya API se encuentra integrada con Canvas.

| 👤 Andrés Cruz

🇺🇸 In english