Una animación CSS es una secuencia automática de cambios visuales definida en una línea de tiempo.
En la entrada anterior vimos transition, que sirve para suavizar cambios entre dos estados. Por ejemplo, un botón que cambia de color al hacer :hover. Eso está muy bien para interacciones sencillas, pero se queda para animaciones más complejas.
Cuando necesitamos movimientos autónomos, bucles, escenas de entrada o cambios con varios pasos intermedios tenemos @keyframes y la propiedad animation
Transición o animación
Antes de lanzarnos a escribir @keyframes como si no hubiera mañana, conviene separar conceptos.
- Una transición necesita un cambio de estado:
:hover, una clase nueva, unfocus, etc. - Una animación puede ejecutarse sola, repetirse, ir hacia delante y hacia atrás, o tener muchos pasos.
/* Transición: espera a que cambie algo */
.boton {
transition: transform 0.2s ease;
}
.boton:hover {
transform: translateY(-2px);
}
/* Animación: tiene su propia línea de tiempo */
.indicador {
animation: parpadeo 1s infinite;
}
Las transiciones son perfectas para microinteracciones. Las animaciones son mejores cuando el movimiento tiene vida propia.
Definir la animación con @keyframes
La regla @keyframes define los momentos importantes de una animación. Es como escribir el guion de lo que va a pasar.
@keyframes latido {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
El navegador calcula automáticamente los valores intermedios entre esos puntos. Nosotros marcamos los fotogramas clave, y él se encarga de dibujar el camino.
0% y 100% también pueden escribirse como from y to. Para animaciones de dos estados queda muy limpio.
@keyframes aparecer {
from {
opacity: 0;
transform: translateY(1rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Aplicar la animación con animation
Definir @keyframes no hace que ocurra nada por sí solo. Necesitamos asociar esa animación a un elemento con la propiedad animation.
.corazon {
animation: latido 2s ease-in-out infinite;
}
Esta propiedad es una abreviatura que puede incluir muchos datos en una sola línea.
animation-name: nombre del@keyframes.animation-duration: duración de un ciclo.animation-timing-function: curva de aceleración.animation-delay: retraso antes de empezar.animation-iteration-count: número de repeticiones.animation-direction: dirección de reproducción.animation-fill-mode: estado que conserva antes o después de animar.
.corazon {
/* nombre | duración | curva | retraso | repeticiones | dirección */
animation: latido 2s ease-in-out 0s infinite normal;
}
Si esa línea os parece demasiado comprimida, podéis escribirlo con propiedades largas.
.corazon {
animation-name: latido;
animation-duration: 2s;
animation-timing-function: ease-in-out;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
}
Repeticiones y dirección
La propiedad animation-iteration-count indica cuántas veces se repite la animación.
.aviso {
animation: sacudida 0.4s ease 3;
}
.cargando {
animation: girar 1s linear infinite;
}
infinite crea un bucle infinito. Usadlo con cariño, porque una animación constante llama mucho la atención. Si todo se mueve todo el rato, la página parece nerviosa.
La propiedad animation-direction controla en qué sentido se reproduce.
.pelota {
animation: rebote 1s ease-in-out infinite alternate;
}
Con alternate, la animación va del 0% al 100% y luego vuelve del 100% al 0%. Es perfecto para movimientos de ida y vuelta.
animation-fill-mode
Por defecto, cuando una animación termina, el elemento vuelve a su estilo original. A veces eso es justo lo que queremos. Otras veces queda fatal, porque el elemento aparece suavemente… y al terminar pega un salto atrás.
Para controlar eso usamos animation-fill-mode.
.modal {
animation: aparecer 0.25s ease-out forwards;
}
forwards hace que el elemento conserve los estilos del último fotograma. Es muy útil para animaciones de entrada.
@keyframes aparecer {
from {
opacity: 0;
transform: translateY(1rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Si una animación empieza con un estado visual distinto, también existe backwards, que aplica el primer fotograma durante el retraso. Y both, que combina forwards y backwards.
Animaciones encadenadas con retrasos
Podemos crear entradas escalonadas aplicando distintos animation-delay.
.item {
opacity: 0;
animation: aparecer 0.4s ease-out forwards;
}
.item:nth-child(1) {
animation-delay: 0s;
}
.item:nth-child(2) {
animation-delay: 0.1s;
}
.item:nth-child(3) {
animation-delay: 0.2s;
}
El resultado es una lista que aparece en cascada. Bien usado queda elegante. Mal usado parece que la interfaz tarda en decidirse a existir.
Rendimiento: animad lo barato
No todas las propiedades cuestan lo mismo de animar. Algunas obligan al navegador a recalcular la distribución de la página una y otra vez.
/* Peor: obliga a recalcular layout */
.caja {
animation: crecer 1s infinite alternate;
}
@keyframes crecer {
to {
width: 400px;
}
}
Para animaciones fluidas, intentad moveros con transform y opacity.
/* Mejor: suele ir mucho más fluido */
.caja {
animation: entrar 0.4s ease-out forwards;
}
@keyframes entrar {
from {
opacity: 0;
transform: translateX(-2rem);
}
to {
opacity: 1;
transform: translateX(0);
}
}
Como norma práctica, animad transform y opacity siempre que podáis. Evitad animar width, height, margin, top o left si buscáis movimientos suaves.
Ejemplo completo: spinner de carga
Un caso clásico de animación CSS es un indicador de carga.
<span class="spinner" aria-label="Cargando"></span>
.spinner {
display: inline-block;
width: 2rem;
height: 2rem;
border: 0.25rem solid #dbeafe;
border-top-color: #2563eb;
border-radius: 50%;
animation: girar 0.8s linear infinite;
}
@keyframes girar {
to {
transform: rotate(360deg);
}
}
El giro se hace con transform, así que es una animación bastante barata para el navegador.
