Transiciones y animaciones

Aprende a animar interfaces con transition para cambios de estado y @keyframes para animaciones continuas. Rendimiento, timing functions y buenas prácticas.

El CSS que no se mueve es una foto. Las transiciones y animaciones añaden la dimensión del tiempo: comunican cambios de estado, guían la atención y hacen que las interfaces se sientan vivas sin necesidad de JavaScript.


Transiciones — cambios de estado suaves

transition interpola el cambio de una propiedad entre dos estados. Se define en el elemento en reposo, no en el estado final:

/* Sintaxis: propiedad duracion timing-function retardo */
.btn {
  background: var(--color-primary);
  transform: translateY(0);
  box-shadow: 2px 2px 0 var(--blackMirror);
  transition: background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.btn:hover {
  background: var(--color-accent);
  transform: translateY(-2px);
  box-shadow: 4px 4px 0 var(--blackMirror);
}

Propiedades de transition

.elemento {
  transition-property: background, transform;  /* qué propiedades */
  transition-duration: 0.2s;                   /* cuánto dura */
  transition-timing-function: ease-out;        /* curva de velocidad */
  transition-delay: 0.05s;                     /* retardo inicial */

  /* Shorthand (el orden importa: propiedad | duración | timing | retardo) */
  transition: background 0.2s ease-out 0.05s;
}

transition: all — úsalo con cuidado

transition: all es cómodo pero peligroso: anima todas las propiedades que cambien, incluyendo width, height o clip-path, que pueden ser costosas de animar. Sé específico.


Timing functions — la curva de velocidad

La timing function define cómo acelera y desacelera la animación:

transition-timing-function: ease;          /* lento → rápido → lento (por defecto) */
transition-timing-function: ease-in;       /* lento al principio */
transition-timing-function: ease-out;      /* lento al final — el más natural */
transition-timing-function: ease-in-out;   /* lento en ambos extremos */
transition-timing-function: linear;        /* velocidad constante */
transition-timing-function: steps(4);      /* saltos discretos — efecto escalonado */

/* Curva personalizada con cubic-bezier */
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* efecto rebote */

Para la mayoría de interacciones de UI, ease-out es la opción más natural: rápida al inicio (parece responsiva) y suave al final (parece física real).


@keyframes — animaciones continuas o complejas

Cuando necesitas más de dos estados o una animación que se repite, usas @keyframes:

@keyframes aparecer {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.tarjeta {
  animation: aparecer 0.3s ease-out forwards;
  /*          nombre   dur  timing  fill-mode */
}

Puedes usar porcentajes para múltiples puntos de control:

@keyframes pulso {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.05); }
  100% { transform: scale(1); }
}

@keyframes parpadeo {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.3; }
}

Propiedades de animation

.elemento {
  animation-name: aparecer;
  animation-duration: 0.4s;
  animation-timing-function: ease-out;
  animation-delay: 0.1s;
  animation-iteration-count: 1;        /* o infinite */
  animation-direction: normal;         /* o reverse, alternate, alternate-reverse */
  animation-fill-mode: forwards;       /* qué estado mantiene al terminar */
  animation-play-state: running;       /* o paused — útil con JS */

  /* Shorthand */
  animation: aparecer 0.4s ease-out 0.1s 1 normal forwards;
}

fill-mode — qué pasa antes y después

animation-fill-mode: none;      /* vuelve al estado original al terminar */
animation-fill-mode: forwards;  /* se queda en el estado final (el más usado) */
animation-fill-mode: backwards; /* aplica el estado inicial durante el delay */
animation-fill-mode: both;      /* combina forwards y backwards */

Animar listas con retraso escalonado

Un patrón muy habitual: animar elementos de una lista con un pequeño retraso entre cada uno:

.lista-item {
  opacity: 0;
  transform: translateY(8px);
  animation: aparecer 0.3s ease-out forwards;
}

.lista-item:nth-child(1) { animation-delay: 0.0s; }
.lista-item:nth-child(2) { animation-delay: 0.05s; }
.lista-item:nth-child(3) { animation-delay: 0.10s; }
.lista-item:nth-child(4) { animation-delay: 0.15s; }

Con CSS custom properties puedes hacerlo sin repetir reglas si el framework permite pasar el índice:

.lista-item {
  animation-delay: calc(var(--index, 0) * 0.05s);
}
<li class="lista-item" style="--index: 0">...</li>
<li class="lista-item" style="--index: 1">...</li>

Rendimiento: qué propiedades animar

No todas las propiedades cuestan lo mismo. El navegador tiene que recalcular el layout si cambias dimensiones, y repintar la pantalla si cambias colores de fondo.

Propiedades baratas (se animan en la GPU, no afectan al layout):

  • transform — mover, escalar, rotar
  • opacity — transparencia

Propiedades costosas (fuerzan repaint o reflow):

  • width, height, margin, padding — reflow
  • background-color, color, box-shadow — repaint

Siempre que puedas sustituir una animación de width por una de transform: scaleX(), hazlo. El resultado visual es similar pero el rendimiento, muy diferente.


Respetar las preferencias del usuario

Algunos usuarios configuran su sistema operativo para reducir el movimiento (por epilepsia, mareo o preferencia). Respétalo:

@keyframes slideIn {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}

.panel {
  animation: slideIn 0.3s ease-out forwards;
}

/* Desactiva las animaciones si el usuario las ha reducido */
@media (prefers-reduced-motion: reduce) {
  .panel {
    animation: none;
  }
}

En la siguiente lección profundizamos en posicionamiento y stacking context: el comportamiento real de sticky, los bugs del z-index y por qué los elementos no se apilan donde esperas.