En esta página

Transiciones y animaciones CSS

12 min lectura TextoCap. 4 — Estilos visuales

Transiciones vs animaciones

CSS ofrece dos mecanismos para agregar movimiento:

  • Transiciones: animan el cambio entre dos estados (por ejemplo, hover)
  • Animaciones con @keyframes: definen secuencias complejas con multiples pasos

Transiciones

Una transición suaviza el cambio de una propiedad CSS de un valor a otro. Se define en el estado base (no en el :hover).

La propiedad transition

.elemento {
  transition: propiedad duracion timing-function delay;
}
Parametro Descripcion Ejemplo
propiedad Que propiedad animar opacity, transform, all
duracion Cuanto dura 200ms, 0.3s
timing-function Curva de velocidad ease, linear, ease-in-out
delay Espera antes de iniciar 0ms, 100ms

Ejemplo práctico

.boton {
  background: #1a1a2e;
  transition: background 200ms ease, transform 150ms ease;
}

.boton:hover {
  background: #3a3a5e;
  transform: translateY(-2px);
}

Multiples transiciones

Separa cada propiedad con coma. Evita transition: all porque puede animar propiedades inesperadas y afectar el rendimiento.

Timing functions

Función Comportamiento
ease Inicio lento, rápido en medio, final lento (defecto)
ease-in Inicio lento, final rápido
ease-out Inicio rápido, final lento
ease-in-out Lento en ambos extremos
linear Velocidad constante
cubic-bezier() Curva personalizada

Para interacciones de usuario, ease-out se siente más natural porque responde rápido al input.

Duraciones recomendadas

Tipo de interacción Duracion
Hover, focus 150-200ms
Apertura de menu 200-300ms
Transición de página 300-500ms
Animación de entrada 400-600ms

Más de 500ms se siente lento. Menos de 100ms es imperceptible.

Animaciones con @keyframes

Para movimientos más complejos, @keyframes permite definir multiples pasos:

@keyframes deslizar-entrada {
  0% {
    opacity: 0;
    transform: translateX(-20px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

.panel {
  animation: deslizar-entrada 400ms ease both;
}

Propiedades de animación

Propiedad Descripcion
animation-name Nombre del @keyframes
animation-duration Duracion total
animation-timing-function Curva de velocidad
animation-delay Espera antes de iniciar
animation-iteration-count Veces que se repite (1, infinite)
animation-direction Direccion (normal, reverse, alternate)
animation-fill-mode Estado final (forwards, backwards, both)

Shorthand

.elemento {
  animation: nombre 400ms ease 0ms 1 normal both;
  /*         name   dur   timing delay count dir fill */
}

Animaciones escalonadas

Para animar una lista de elementos con un efecto en cascada, usa animation-delay incrementado:

.item:nth-child(1) { animation-delay: 0ms; }
.item:nth-child(2) { animation-delay: 75ms; }
.item:nth-child(3) { animation-delay: 150ms; }

O con custom properties para mayor flexibilidad:

.item {
  animation: aparecer 400ms ease both;
  animation-delay: calc(var(--i) * 75ms);
}
<li class="item" style="--i: 0">Primero</li>
<li class="item" style="--i: 1">Segundo</li>
<li class="item" style="--i: 2">Tercero</li>

Rendimiento de animaciones

El navegador puede animar eficientemente solo dos propiedades sin causar repaint:

  • transform (translate, scale, rotate)
  • opacity

Animar width, height, margin, top, left fuerza al navegador a recalcular el layout de toda la página (reflow), lo cual es costoso.

Accesibilidad: prefers-reduced-motion

Algunos usuarios experimentan mareos o malestar con animaciones. Respeta su preferencia:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Las animaciones dan vida a tu interfaz. En la siguiente leccion aprenderemos custom properties, las variables nativas de CSS que hacen tu código mantenible y dinámico.

Práctica

  1. Anade transiciones a un boton: Crea un boton con transiciones en background y transform al hacer hover. Usa ease-out y una duracion de 200ms para una sensacion natural.
  2. Crea un spinner de carga: Implementa un spinner circular usando @keyframes con una rotacion de 360 grados y animation: spin 800ms linear infinite.
  3. Respeta prefers-reduced-motion: Anade un bloque @media (prefers-reduced-motion: reduce) que reduzca la duracion de todas las animaciones y transiciones a 0.01ms.
Rendimiento
Solo anima propiedades que el navegador puede optimizar con la GPU: transform (translate, scale, rotate) y opacity. Evita animar width, height, margin o padding porque causan reflow y son más costosos.
Accesibilidad del movimiento
Siempre incluye @media (prefers-reduced-motion: reduce) para respetar a usuarios que tienen sensibilidad al movimiento. Reduce o elimina las animaciones en ese caso.
/* Transición básica en boton */
.boton {
  background: #1a1a2e;
  color: white;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background 200ms ease,
              transform 150ms ease;
}

.boton:hover {
  background: #2d2d44;
  transform: translateY(-2px);
}

.boton:active {
  transform: translateY(0) scale(0.98);
  transition-duration: 50ms;
}

/* Card con multiples transiciones */
.card {
  background: white;
  border-radius: 12px;
  padding: 1.5rem;
  border: 1px solid #e0e0e0;
  transition: border-color 200ms ease,
              box-shadow 300ms ease;
}

.card:hover {
  border-color: #b056ff;
  box-shadow: 0 8px 24px rgb(176 86 255 / 15%);
}

/* Transición de entrada con delay escalonado */
.lista-item {
  opacity: 0;
  transform: translateY(10px);
  animation: aparecer 400ms ease forwards;
}

.lista-item:nth-child(1) { animation-delay: 0ms; }
.lista-item:nth-child(2) { animation-delay: 75ms; }
.lista-item:nth-child(3) { animation-delay: 150ms; }
.lista-item:nth-child(4) { animation-delay: 225ms; }
/* Animación de aparicion */
@keyframes aparecer {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.sección {
  animation: aparecer 600ms ease both;
}

/* Spinner de carga */
@keyframes girar {
  to { rotate: 1turn; }
}

.spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #e0e0e0;
  border-top-color: #b056ff;
  border-radius: 50%;
  animation: girar 800ms linear infinite;
}

/* Pulso en notificación */
@keyframes pulso {
  0%, 100% { scale: 1; }
  50% { scale: 1.15; }
}

.badge-notificación {
  background: #e6286a;
  color: white;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  animation: pulso 2s ease-in-out infinite;
}

/* Respetar preferencias del usuario */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}