Animaciones con JavaScript
Aprende animaciones con JavaScript: requestAnimationFrame, funciones de easing, encadenamiento, la Web Animations API, rendimiento y accesibilidad.
Las animaciones con JavaScript ofrecen una forma dinámica de mejorar la experiencia del usuario en las páginas web. Desde efectos sutiles hasta movimientos complejos, dominar las animaciones con JavaScript puede elevar notablemente la interactividad y el atractivo visual de tus proyectos. Esta guía completa ofrece una exploración en profundidad de las animaciones con JavaScript, con los conceptos fundamentales, las técnicas esenciales y ejemplos prácticos que te ayudarán a crear animaciones atractivas con soltura.
Introducción a las animaciones con JavaScript
Las animaciones con JavaScript manipulan elementos del Document Object Model (DOM) para crear efectos visuales. Se utilizan para mover, redimensionar y modificar elementos en respuesta a las interacciones del usuario o de forma automática con el paso del tiempo. Si comprendes los principios y las técnicas básicas, podrás crear animaciones que sean a la vez eficientes y visualmente atractivas.
¿Por qué usar JavaScript para las animaciones?
Las animaciones con JavaScript ofrecen más control y flexibilidad que las animaciones CSS. Permiten secuencias e interacciones más complejas, lo que hace posible crear efectos muy personalizados. Las animaciones con JavaScript pueden controlarse de forma dinámica y suelen ser necesarias cuando hay que responder a las acciones del usuario o integrarse con otras funcionalidades de JavaScript.
Como regla general:
- Recurre a CSS (
transition/@keyframes) para cambios de estado simples y declarativos, como efectos hover, desvanecimientos y interruptores. El navegador los optimiza en un hilo independiente del compositor, por lo que suelen ser la opción más eficiente. Consulta animaciones CSS. - Recurre a JavaScript cuando la animación dependa de datos, gestos del usuario, física o lógica que CSS no puede expresar, por ejemplo seguir el ratón, ajustarse a posiciones dinámicas o encadenar muchos pasos con condiciones ramificadas.
Para la opción de JavaScript que menos esfuerzo requiere, la Web Animations API integrada (que se explica más abajo) ofrece el rendimiento de CSS con control imperativo, mientras que requestAnimationFrame te da control total fotograma a fotograma cuando lo necesitas.
Conceptos básicos de las animaciones con JavaScript
Antes de entrar en técnicas concretas, conviene entender algunos conceptos básicos en los que se apoyan las animaciones con JavaScript:
- Fotogramas por segundo (FPS): la frecuencia a la que se muestran los fotogramas de la animación. Un FPS más alto da como resultado animaciones más fluidas.
- Funciones de easing: estas funciones controlan la aceleración y la desaceleración de las animaciones y aportan un movimiento natural.
- Funciones de temporización: determinan la duración y el retraso de las animaciones.
- Fotogramas clave (keyframes): definen los estados inicial, final e intermedios de una animación.
requestAnimationFrame frente a setInterval
Antes de que existiera requestAnimationFrame, las animaciones se gestionaban con setInterval y setTimeout, que ejecutaban un callback cada pocos milisegundos. Este enfoque tiene dos problemas: el intervalo no está sincronizado con la frecuencia de refresco de la pantalla (lo que provoca fotogramas perdidos o duplicados) y el temporizador sigue disparándose aunque la pestaña esté en segundo plano, con el consiguiente gasto de CPU y batería.
requestAnimationFrame(callback) resuelve ambos problemas. El navegador llama a tu callback justo antes del siguiente repintado (normalmente unas 60 veces por segundo, al ritmo de la pantalla) y se pausa automáticamente cuando la pestaña está oculta. El callback recibe un argumento timestamp de alta resolución, que sirve para calcular el progreso a partir del tiempo real transcurrido, en lugar de suponer una frecuencia de fotogramas fija.
// setInterval: fixed timer, not synced to the screen, runs in background tabs
let pos = 0;
const id = setInterval(() => {
pos += 2;
if (pos >= 200) clearInterval(id);
}, 16);
// requestAnimationFrame: synced to repaint, pauses in background tabs
function frame(timestamp) {
// use timestamp to drive the animation
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);Usa requestAnimationFrame para cualquier cosa que actualice los elementos visuales en cada fotograma.
Ejemplo sencillo de animación con JavaScript
Empecemos con un ejemplo sencillo en el que animamos un cuadrado que se desplaza por la pantalla.
<!DOCTYPE html>
<html>
<head>
<style>
#square {
width: 50px;
height: 50px;
background-color: red;
position: absolute;
left: 0;
top: 50px;
}
</style>
</head>
<body>
<div id="square"></div>
<script>
const square = document.getElementById('square');
let pos = 0;
function move() {
if (pos < window.innerWidth - 50) {
pos += 2;
square.style.transform = 'translateX(' + pos + 'px)';
requestAnimationFrame(move);
}
}
move();
</script>
</body>
</html>Este ejemplo muestra una animación básica en la que un cuadrado rojo se mueve del lado izquierdo de la pantalla al derecho. La animación utiliza la función requestAnimationFrame, que garantiza un movimiento fluido al sincronizar la animación con la frecuencia de refresco de la pantalla. La función move actualiza la posición del cuadrado de forma incremental y se llama a sí misma continuamente hasta que el cuadrado llega al borde de la ventana.
Aunque las animaciones con JS requieren actualizaciones frecuentes de estilo, prefiere transform y opacity para evitar desencadenar reflows de layout.
Técnicas avanzadas en las animaciones con JavaScript
Uso de funciones de easing
Las funciones de easing hacen que las animaciones parezcan más naturales al variar la velocidad de la animación. Normalmente esperan un valor de progreso normalizado entre 0 y 1. Este es un ejemplo que usa una función de easing para animar el cuadrado:
<!DOCTYPE html>
<html>
<head>
<style>
#square {
width: 50px;
height: 50px;
background-color: blue;
position: absolute;
left: 0;
top: 50px;
}
</style>
</head>
<body>
<div id="square"></div>
<script>
const square = document.getElementById('square');
let start = null;
const duration = 2000; // Animation duration in milliseconds
function easeOutQuad(t) {
return t * (2 - t);
}
function animate(timestamp) {
if (!start) start = timestamp;
let elapsed = timestamp - start;
let progress = Math.min(elapsed / duration, 1);
let easedProgress = easeOutQuad(progress);
square.style.transform = 'translateX(' + (window.innerWidth - 50) * easedProgress + 'px)';
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
</script>
</body>
</html>Este ejemplo mejora la animación sencilla incorporando una función de easing llamada easeOutQuad. Las funciones de easing crean animaciones de aspecto más natural al ajustar con el tiempo la velocidad del elemento animado. En este caso, el cuadrado azul se mueve de izquierda a derecha: empieza rápido y va frenando a medida que se acerca al final. La duración de la animación es de 2000 milisegundos y la posición del cuadrado se actualiza en función del progreso suavizado.
Encadenamiento de animaciones
Encadenar animaciones permite ejecutar varias animaciones de forma secuencial. Este es un ejemplo de un cuadrado que primero se mueve a la derecha y después hacia abajo:
<!DOCTYPE html>
<html>
<head>
<style>
#square {
width: 50px;
height: 50px;
background-color: green;
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div id="square"></div>
<script>
const square = document.getElementById('square');
let xPos = 0;
let yPos = 0;
function moveRight() {
if (xPos < window.innerWidth - 50) {
xPos += 2;
square.style.transform = 'translateX(' + xPos + 'px)';
requestAnimationFrame(moveRight);
} else {
xPos = 0;
yPos = 0; // Explicitly reset yPos before downward phase
square.style.transform = 'translateX(0px)';
requestAnimationFrame(moveDown);
}
}
function moveDown() {
if (yPos < window.innerHeight - 50) {
yPos += 2;
square.style.transform = 'translateY(' + yPos + 'px)';
requestAnimationFrame(moveDown);
} else {
yPos = 0; // Reset for repeated cycles
requestAnimationFrame(moveRight);
}
}
moveRight();
</script>
</body>
</html>Este ejemplo muestra el encadenamiento de animaciones moviendo un cuadrado verde primero hacia la derecha y después hacia abajo. La función moveRight desplaza el cuadrado a la derecha hasta que llega al borde de la ventana. Una vez que alcanza el borde derecho, yPos se restablece explícitamente a 0 antes de llamar a moveDown. Esta ejecución secuencial, junto con una gestión adecuada del estado para los ciclos repetidos, muestra cómo crear secuencias de animación más complejas encadenando varias animaciones.
Animación de varias propiedades
Animar varias propiedades a la vez puede crear efectos más complejos y visualmente atractivos. Este es un ejemplo que cambia tanto la posición como el color:
<!DOCTYPE html>
<html>
<head>
<style>
#square {
width: 50px;
height: 50px;
background-color: purple;
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div id="square"></div>
<script>
const square = document.getElementById('square');
let pos = 0;
function changeColor(progress) {
let red = Math.floor(255 * progress);
let green = Math.floor(255 * (1 - progress));
return `rgb(${red}, ${green}, 0)`;
}
function animate() {
if (pos < window.innerWidth - 50) {
pos += 2;
let progress = pos / (window.innerWidth - 50);
square.style.transform = 'translateX(' + pos + 'px)';
square.style.backgroundColor = changeColor(progress);
requestAnimationFrame(animate);
}
}
animate();
</script>
</body>
</html>Este ejemplo anima tanto la posición como el color de fondo de un cuadrado morado. A medida que el cuadrado se mueve de izquierda a derecha, su color pasa de rojo a verde. La función changeColor calcula el color en función del progreso de la animación y crea un efecto de degradado. Así se demuestra cómo animar varias propiedades de forma simultánea, lo que añade complejidad e interés visual a las animaciones.
La Web Animations API
Programar a mano un bucle con requestAnimationFrame te da control total, pero para la mayoría de las animaciones basadas en una línea de tiempo el navegador ya dispone de un motor integrado: la Web Animations API (WAAPI). Al llamar a element.animate(keyframes, options), la animación se ejecuta fuera del hilo principal cuando es posible (igual que en CSS), de modo que obtienes una reproducción fluida sin tener que escribir un bucle de fotogramas. Además, devuelve un objeto Animation que puedes pausar, invertir, desplazar a otro instante o esperar.
<!DOCTYPE html>
<html>
<head>
<style>
#box {
width: 50px;
height: 50px;
background-color: teal;
position: absolute;
left: 0;
top: 50px;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
const box = document.getElementById('box');
const animation = box.animate(
[
{ transform: 'translateX(0)', opacity: 1 },
{ transform: 'translateX(300px)', opacity: 0.3 }
],
{
duration: 2000,
easing: 'ease-out',
fill: 'forwards'
}
);
// The returned object gives you imperative control:
animation.finished.then(() => console.log('done'));
// animation.pause(); animation.reverse(); animation.currentTime = 1000;
</script>
</body>
</html>El primer argumento es un array de fotogramas clave (cada uno es un objeto con propiedades CSS); el segundo establece duration, easing y comportamientos como fill (mantener el estado final), iterations, direction y delay. Como easing acepta los mismos valores cubic-bezier() y palabras clave que CSS, muchas veces no hace falta escribir a mano ninguna matemática de easing.
Prefiere WAAPI cuando tu animación sea una línea de tiempo fija de cambios de propiedades. Reserva un bucle manual con requestAnimationFrame para los casos en que cada fotograma dependa de una entrada en vivo o de un estado calculado, como la física, el arrastre o valores que no se conocen de antemano.
Detener e invertir animaciones
Un bucle con requestAnimationFrame sigue ejecutándose hasta que dejas de programar nuevos fotogramas. Para cancelarlo de inmediato, guarda el ID que devuelve requestAnimationFrame y pásalo a cancelAnimationFrame:
let rafId;
function loop(timestamp) {
// ...update visuals...
rafId = requestAnimationFrame(loop);
}
rafId = requestAnimationFrame(loop);
// Later, e.g. on a button click or when the element is removed:
cancelAnimationFrame(rafId);Cancela siempre las animaciones en curso antes de eliminar sus elementos del DOM; de lo contrario, el bucle seguirá modificando un nodo desvinculado y desperdiciará trabajo en cada fotograma. Con la Web Animations API, los equivalentes son simplemente animation.cancel(), animation.pause() y animation.reverse().
Accesibilidad: respeta la preferencia de movimiento reducido
El movimiento amplio o rápido puede provocar náuseas, mareos o migrañas en algunos usuarios. Los sistemas operativos exponen una preferencia de «reducir movimiento» que conviene respetar. En JavaScript, léela con matchMedia y desactiva la animación o salta directamente al estado final:
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (prefersReducedMotion) {
// Skip the animation: apply the final state instantly
square.style.transform = 'translateX(300px)';
} else {
requestAnimationFrame(animate);
}Esta única comprobación hace que tus interfaces sean más inclusivas y más agradables para los usuarios que prefieren no tener movimiento.
Optimización del rendimiento de las animaciones con JavaScript
Para garantizar animaciones fluidas y eficientes, sigue estos consejos de optimización del rendimiento:
- Usa
requestAnimationFrame: este método se sincroniza con la frecuencia de refresco de la pantalla y ofrece animaciones más fluidas. - Minimiza las manipulaciones del DOM: agrupa las actualizaciones del DOM para reducir los reflows y los repintados. Prefiere
transformyopacitypara los cambios de posición y aprovechar así la aceleración por GPU. - Optimiza CSS: usa propiedades CSS aceleradas por GPU, como
transformyopacity. - Limita la frecuencia de los eventos: restringe la frecuencia de eventos como
resizeoscrollmediante técnicas de throttling o debouncing. - Detén las animaciones correctamente: usa
cancelAnimationFramepara detener las animaciones cuando ya no sean necesarias. Guarda el ID de la animación y llama acancelAnimationFrame(id)para evitar fugas de memoria. Ejemplo:let animationId = requestAnimationFrame(animate); // ... later ... cancelAnimationFrame(animationId); - Evita el layout thrashing: no leas propiedades de layout (por ejemplo,
offsetWidthogetBoundingClientRect) dentro del bucle de animación, ya que esto obliga al navegador a recalcular los estilos a mitad de fotograma y puede provocar tirones.
También puedes crear animaciones solo con CSS (sin JavaScript), y normalmente se consigue un mejor rendimiento. Consulta animaciones CSS.
Conclusión
Dominar las animaciones con JavaScript implica comprender los principios básicos, aplicar técnicas avanzadas y optimizar el rendimiento. Si sigues esta guía, podrás crear animaciones atractivas y receptivas que mejoren la experiencia del usuario. Experimenta con distintas funciones de easing, encadenamientos y animaciones de propiedades para aprovechar todo el potencial de JavaScript en tus proyectos web.
Para seguir profundizando, explora estos temas relacionados:
- Animaciones CSS: la alternativa declarativa y, a menudo, más eficiente.
- Planificación: setTimeout y setInterval: programación basada en temporizadores y por qué se prefiere
requestAnimationFramepara los elementos visuales. - Introducción a los eventos del navegador: activa animaciones en respuesta a la interacción del usuario.
- JavaScript HTML DOM: selecciona y manipula los elementos que animas.