Animar elementos del DOM

Animaciones con Javascript (Web Animations)


Mediante CSS se pueden crear animaciones CSS muy complejas y potentes. Sin embargo, es posible que en casos donde buscamos cosas muy específicas, necesitemos la potencia de Javascript para poder realizar animaciones avanzadas, ahí es donde entra Web Animations, que no es más que una forma de crear animaciones CSS desde Javascript.

Web Animations

Para crear una animación utilizaremos el método .animate(), que se ejecuta sobre un elemento del DOM. Tenemos varias formas de utilizarlo, vamos a verlo en la tabla siguiente y en un ejemplo sencillo para empezar.

El método .animate() tiene varias modalidades, vamos a explicar primero la más sencilla:

Método Descripción
.animate(keyframes,duration) Crea y aplica una animación CSS que dura duration segundos.
.animate(keyframes,options) Crea y aplica una animación CSS, con las opciones indicadas en options.

Una vez tenemos el elemento donde queremos aplicar la animación, ejecutamos el método animate() el cual va a tener dos parámetros:

  • El primer parámetro: Un objeto keyframes, que son los fotogramas clave de la animación, con los cambios CSS que se irán aplicando. Equivale a la regla @keyframes de CSS.

  • El segundo parámetro: Un número duration, con la duración en milisegundos de la animación. Más adelante, veremos que este segundo parámetro puede ser también un objeto de opciones, con el que definir cosas más complejas. Lo veremos más adelante.

El siguiente fragmento de código Javascript busca un elemento con clase .element y le aplica una animación que mueve el elemento hacia la derecha, luego hacia abajo, luego hacia la izquierda y luego hacia arriba, al punto de partida. Siempre en cantidades de 200px:

const element = document.querySelector(".element");

const keyframes = [
  { transform: "translate(0, 0)" },
  { transform: "translate(200px, 0)" },
  { transform: "translate(200px, 200px)" },
  { transform: "translate(0, 200px)" }
];

element.animate(keyframes, 4000);

Esto sería más o menos equivalente a cuando creamos una animación con CSS de la siguiente forma:

@keyframes move {
  0% { transform: translate(0, 0); }
  40% { transform: translate(200px, 0); }
  60% { transform: translate(200px, 200px); }
  80% { transform: translate(0, 200px); }
}

Si desconoces las animaciones CSS, sería aconsejable primero echar un vistazo antes de continuar, ya que vamos a asumir ciertos conocimientos: Aprender animaciones CSS.

Keyframes: Los fotogramas clave

En el ejemplo anterior habrás visto que hemos indicado una constante keyframes, donde está toda la información de la animación. Ese objeto equivale a la regla @keyframes de CSS con sus propiedades CSS en cada uno de los fotogramas clave o intervalos que componen la animación.

Ese parámetro puede pasarse al método .animate() de dos formas. Vamos a analizar estas dos modalidades a continuación.

Modalidad 1: Array de objetos

El parámetro keyframes es un , es decir, un array de objetos, donde cada objeto es un fotograma clave de la animación.

const keyframes = [
  {
    transform: "translate(0, 0)",
    opacity: 0,
    backgroundColor: "#842911"
  },
  {
    transform: "translate(200px, 0)",
    opacity: 1
  },
  {
    transform: "translate(200px, 200px)",
    opacity: 0.25,
    backgroundColor: "#840123"
  },
  {
    transform: "translate(0, 200px)",
    opacity: 1
  }
];

Observa que cada propiedad de cada objeto es una propiedad CSS, pero escrita en camelCase. En el caso de transform y opacity no hace falta, pero en el caso de la propiedad background-color, en este caso, se reescribe a backgroundColor. Los valores normalmente son de tipo o de tipo , lo que también nos permite interpolar variables o añadir contenido guardado en otras estructuras de datos.

Modalidad 2: Objeto con arrays

El parámetro keyframes también puede indicarse como un , es decir, un objeto donde cada propiedad del mismo contiene un array con los valores de esa propiedad CSS durante cada fotograma clave de la animación.

const keyframes = {
  transform: [
    "translate(0, 0)",
    "translate(200px, 0)",
    "translate(200px, 200px)",
    "translate(0, 200px)"
  ],
  opacity: [0, 1, 0.25, 1],
  backgroundColor: ["#842911", "#840123"]
};

La mecanica es la misma que la anterior, sólo que varía la forma de escribirlo. Utiliza la que mejor se adapte a tus necesidades.

Propiedades del keyframe

Cada objeto de un fotograma clave o intervalo de la animación, aparte de las propiedades CSS transformadas a camelCase, puede tener alguna de las propiedades que comentaremos a continuación. Mediante ellas, podremos establecer particularidades a ese fotograma en cuestión:

Propiedades Descripción
offset Indica el porcentaje de cada intervalo en los @keyframes de una animación CSS.
easing Indica la función de tiempo que se aplicará al intervalo concreto de la animación.
composite Indica la operación de composición que se aplicará en el resto de intervalos.

La propiedad offset nos permite indicar en que momento concreto empieza un intervalo o fotograma clave. Por otro lado, easing nos permite activar una función de tiempo (ritmo) de la animación para un fotograma clave específico. Veremos que también se puede indicar en un objeto de opciones que explicaremos más adelante, y que afecte a todos los fotogramas clave de la animación.

Por último, tenemos composite, que es una operación de composición para el fotograma clave concreto. Hablaremos de esta característica más adelante.

Offset: Inicio del fotograma

Probablemente, te hayas preguntando como puedes variar el instante en el que comienzan cada uno de los fotogramas clave de la animación. En CSS, estableciamos un porcentaje en el interior de las reglas @keyframes que establecían este comienzo.

Aquí, esta información la establecemos en la propiedad offset:

const element = document.querySelector(".element");

const keyframes = [
  { transform: "translate(0, 0)", offset: 0 },
  { transform: "translate(200px, 0)", offset: 0.40 },
  { transform: "translate(200px, 200px)", offset: 0.60 },
  { transform: "translate(0, 200px)", offset: 0.80 }
];

element.animate(keyframes, 4000);

Observa que en este fragmento de código, además de la propiedad transform hemos establecido una propiedad offset para determinar el inicio del fotograma. Dicha propiedad debe contener un valor desde 0 a 1, con decimales. Esta es la equivalencia a los porcentajes. Por ejemplo, si tenemos 80% en la regla @keyframes, la equivalencia en este caso sería un offset de 0.8.

Las propiedades CSS offset y float no se pueden definir ya que coinciden con la propiedad offset que acabamos de mencionar, y con la palabra reservada float, por lo que si queremos establecerlas, debe hacerse como cssOffset y cssFloat.

Opciones

Antes habrás comprobado que como segundo parámetro de animate() establecíamos la duración de la animación. Sin embargo, en lugar de ese , podemos pasarle un objeto de opciones, donde configuramos como queremos que se comporte. Entre otras, hay muchas de las propiedades que tienen las animaciones CSS.

Nuestro objeto de opciones podría tener las siguientes:

Propiedades Descripción Equivalencia CSS Default
delay Tiempo (retardo) en ms para que empiece la animación. animation-delay 0
endDelay Tiempo (retardo) en ms para finalizar la animación. 0
duration Duración en ms de la animación (por cada iteración). animation-duration auto
direction Dirección de la animación por sus keyframes. animation-direction normal
easing Función de tiempo (ritmo) de la animación. animation-timing-function linear
fill Comportamiento de la animación al terminar. animation-fill-mode auto
iterationStart Punto de la iteración en la que empieza la animación. 0.0
iterations Número de veces que se repite la animación. animation-iteration-count 1.0
iterationComposite Operación de composición entre cada intervalo. replace
composite Operación de composición entre una y otra animación. animation-composition replace
pseudoElement Si se indica ::before o ::after, se aplica la animación al pseudoelemento.

Por si no los conoces, algunas propiedades anteriores pueden tomar varios valores posibles. Aquí puedes ver una lista de opciones posibles:

Propiedad Valores
direction normal, reverse, alternate, alternate-reverse.
easing linear, ease-in, ease-out, ease-in-out, cubic-bezier(...).
fill none, forwards, backwards, both, auto.
composite add, accumulate, replace.
iterationComposite accumulate, replace.

Veamos ahora, un ejemplo utilizando el objeto de opciones en lugar del parámetro de duración directamente. Simplemente añadimos como segundo parámetro de animate() el objeto de opciones:

const element = document.querySelector(".element");

const keyframes = [
  { transform: "translate(0, 0)", offset: 0 },
  { transform: "translate(200px, 0)", offset: 0.40 },
  { transform: "translate(200px, 200px)", offset: 0.60 },
  { transform: "translate(0, 200px)", offset: 0.80 }
];

const options = {
  duration: 4000,
  direction: "alternate",
  fill: "forwards",
  iterations: Infinity
};

const animation = element.animate(keyframes, options);

Recuerda que puedes utilizar cualquiera de los dos tipos de keyframes que hemos explicado.

El objeto Animation

Habrás observado en el último ejemplo, que el valor devuelto por el método .animate() lo estamos guardando en la constante animation. Esta constante es un objeto Animation, mediante el cuál podemos gestionar ciertas acciones con la animación generada en cuestión.

Entre otras cosas, podemos encontrar las siguientes propiedades:

Propiedad Descripción
startTime Hora (timestamp) en la que la animación ha empezado o empezará a reproducirse.
currentTime Número de ms en el que se encuentra la animación. Si está inactiva, null.
id Cadena de texto para identificar la animación.
finished Indica si la animación ha terminado.
ready Indica si la animación está lista.
pending Indica si la animación está pendiente y no está lista aún.
playState Estado actual de la animación. Puede ser idle, running, paused o finished.
playbackRate Velocidad de reproducción de la animación. Por defecto, 1, velocidad normal.
replaceState Indica estado de animación. Puede ser active, persisted o removed.

Especialmente interesantes son las propiedades .ready y finished, que devuelven una promesa mediante la cuál podemos saber cuando una animación está lista para iniciarse o cuando ha terminado, y por ejemplo podemos encadenar con otra acción.

Por su parte, startTime nos devuelve el momento en el que empezó a reproducirse (o una fecha en el futuro para programar la animación), mientras que currentTime nos dice los segundos totales que han transcurrido de la animación:

const animation = element.animate(keyframes, options);

animation.ready.then(() => {
  console.log(`¡Animación preparada!`);
});

animation.finished.then(() => {
  console.log(`¡Animación terminada! Han pasado ${animation.currentTime}ms.`)
});

Mediante algunos métodos como play(), pause(), finish() o cancel() podemos controlar las animaciones, terminarlas o cancelarlas. Observa también que tienen algunos eventos asociados para detectar cuando ocurre.

Método Descripción Evento asociado
Métodos
play() Inicia o reanuda la animación.
pause() Detiene temporalmente la animación.
finish() Termina la animación. finish: Se dispara el evento al terminar la animación.
cancel() Aborta la animación. cancel: Se dispara el evento al abortar la animación.
remove: Se dispara el evento al eliminar la animación automáticamente.
persist() Indica que la animación sea persistente y no se elimine automáticamente al iniciar otra.
reverse() Invierte la dirección de reproducción.
updatePlaybackRate() Hace efectiva la velocidad de animación indicada en playbackRate.

Observa que los navegadores por defecto, al tener animaciones encadenadas, al terminar una animación e iniciar otra, eliminan la anterior automáticamente. Cuando esto ocurre, se dispara un evento remove que podemos utilizar para gestionar tareas cuando ocurren estas eliminaciones. Además, con el método persist() podemos establecer que no se eliminen automáticamente.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev