Tabla de contenido
- Transitions vs Animations
- CSS Transitions
- CSS Animations con @keyframes
- Propiedades de animation
- Ejemplos prácticos
- Accesibilidad y prefers-reduced-motion
Las animaciones web hace unos años requerían JavaScript o Flash. Hoy, CSS tiene todo lo necesario para crear animaciones fluidas, performantes y accesibles. Desde efectos hover simples hasta loaders complejos, CSS lo maneja solo.
Transitions vs Animations
Antes de entrar en código, conviene entender la diferencia entre los dos mecanismos:
transition: anima el cambio de un estado a otro (de A a B). Necesita un detonador (hover, focus, clase que cambia). Es simple y directo.
@keyframes + animation: define una secuencia de estados (A → B → C → …). Puede correr automáticamente, en bucle, sin necesidad de interacción del usuario.
Regla general: usa transition para efectos de interacción, animation para movimientos continuos o secuencias complejas.
CSS Transitions
La propiedad transition anima suavemente el cambio de cualquier propiedad CSS:
.boton {
background-color: #E53E3E;
color: white;
padding: 12px 24px;
border-radius: 8px;
/* Sintaxis: transition: propiedad duración timing-function delay */
transition: background-color 0.3s ease;
}
.boton:hover {
background-color: #c53030;
}
Puedes animar múltiples propiedades:
.tarjeta {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition:
transform 0.3s ease,
box-shadow 0.3s ease;
}
.tarjeta:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0,0,0,0.2);
}
O usar all para animar todas las propiedades que cambien (con precaución, puede ser costoso):
.elemento {
transition: all 0.25s ease-in-out;
}
Funciones de timing comunes:
transition-timing-function: ease; /* suave en ambos extremos (por defecto) */
transition-timing-function: ease-in; /* lento al inicio, rápido al final */
transition-timing-function: ease-out; /* rápido al inicio, lento al final */
transition-timing-function: ease-in-out; /* lento en ambos extremos */
transition-timing-function: linear; /* velocidad constante */
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* personalizado */
CSS Animations con @keyframes
@keyframes define la secuencia de la animación:
@keyframes nombre-animacion {
from { /* estado inicial */ }
to { /* estado final */ }
}
/* O con porcentajes para múltiples pasos: */
@keyframes pulsar {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
Luego aplicas la animación con la propiedad animation:
.icono {
animation: pulsar 1.5s ease-in-out infinite;
}
Propiedades de animation
.elemento {
animation-name: pulsar; /* nombre del @keyframes */
animation-duration: 1.5s; /* duración de un ciclo */
animation-timing-function: ease; /* curva de velocidad */
animation-delay: 0.5s; /* espera antes de iniciar */
animation-iteration-count: 3; /* repeticiones (infinite = infinito) */
animation-direction: alternate; /* normal | reverse | alternate | alternate-reverse */
animation-fill-mode: forwards; /* none | forwards | backwards | both */
animation-play-state: running; /* running | paused */
}
/* Shorthand: */
.elemento {
animation: pulsar 1.5s ease 0.5s 3 alternate forwards;
}
animation-fill-mode: forwards hace que el elemento se quede en el estado final después de que la animación termina.
Ejemplos prácticos
Spinner/loader:
@keyframes girar {
to { transform: rotate(360deg); }
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(229, 62, 62, 0.2);
border-top-color: #E53E3E;
border-radius: 50%;
animation: girar 0.8s linear infinite;
}
Fade-in al cargar:
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.hero-content {
animation: fadeIn 0.6s ease-out;
}
Shake (efecto de error):
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-8px); }
20%, 40%, 60%, 80% { transform: translateX(8px); }
}
.input-error {
animation: shake 0.5s ease-in-out;
}
Texto parpadeante (cursor de escritura):
@keyframes parpadear {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.cursor {
display: inline-block;
width: 2px;
height: 1.2em;
background: #E53E3E;
animation: parpadear 1s step-end infinite;
}
Skeleton loading (mientras carga contenido):
@keyframes skeleton {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(
90deg,
#1A1B1D 25%,
#222426 50%,
#1A1B1D 75%
);
background-size: 200% 100%;
animation: skeleton 1.5s infinite;
border-radius: 8px;
}
Accesibilidad y prefers-reduced-motion
No todos los usuarios pueden tolerar animaciones. Las personas con epilepsia fotosensible, vértigo o ciertos trastornos neurológicos pueden sufrir con animaciones intensas.
El media query prefers-reduced-motion detecta si el usuario configuró en su sistema operativo que prefiere menos movimiento:
/* Animaciones por defecto */
.elemento {
animation: fadeIn 0.6s ease-out;
transition: transform 0.3s ease;
}
/* Para usuarios que prefieren menos movimiento */
@media (prefers-reduced-motion: reduce) {
.elemento {
animation: none;
transition: none;
}
/* O una alternativa más sutil: */
.spinner {
animation-duration: 2s; /* más lento */
}
}
Esto es una buena práctica en cualquier proyecto web profesional. Y en Astro o cualquier framework moderno, también puedes chequear esta preferencia con JavaScript:
const prefiereMenosMovimiento = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
Las animaciones CSS bien usadas hacen que tu interfaz se sienta viva y profesional. El truco está en la sutileza: las mejores animaciones son las que el usuario apenas nota conscientemente, pero que hacen la experiencia más fluida.