Tema
Transiciones y transformaciones
Un botón que cambia de color de golpe se siente brusco; uno que lo hace en 200 milisegundos se siente pulido. En esta lección aprenderás a suavizar cambios con transiciones y a mover, rotar, escalar e inclinar elementos (incluso en 3D) con transformaciones, cuidando el rendimiento.
Concepto
- Una transición anima el paso de un valor a otro cuando una propiedad cambia (por ejemplo, al hacer
:hover). Tú defines el estado inicial y el final; el navegador calcula los fotogramas intermedios. - Una transformación modifica cómo se dibuja un elemento (posición, tamaño, rotación) sin afectar al resto del layout.
Analogía
Una transición es la diferencia entre encender una luz con un interruptor normal (cambio instantáneo) y con un regulador (dimmer) que la sube poco a poco. Una transformación es mover una foto pegada con imán sobre el refrigerador: la foto se mueve, pero el refrigerador sigue igual.
Transiciones
css
.boton {
background: #7c3aed;
transition: background-color 0.3s ease; /* propiedad | duración | función de tiempo */
}
.boton:hover {
background: #5b21b6; /* el cambio ahora es suave */
}La transición se declara en el estado base, no en el :hover. Así se anima tanto al entrar como al salir.
Propiedades individuales
css
.tarjeta {
transition-property: transform, box-shadow;
transition-duration: 0.3s, 0.5s; /* una duración por propiedad */
transition-timing-function: ease-out;
transition-delay: 0s, 0.1s; /* espera antes de empezar */
}
/* Atajo con varias propiedades */
.tarjeta {
transition:
transform 0.3s ease-out,
box-shadow 0.3s ease-out,
opacity 0.2s linear 0.1s; /* con retraso de 0.1s */
}Evita transition: all
transition: all 0.3s anima cualquier propiedad que cambie, incluidas algunas que no esperabas (y otras costosas, como width). Es más predecible y eficiente listar las propiedades concretas.
Funciones de tiempo (easing)
Definen la aceleración del movimiento:
| Función | Comportamiento | Ideal para |
|---|---|---|
linear | Velocidad constante | Rotaciones infinitas, barras de progreso |
ease | Arranca rápido, frena al final (por defecto) | Uso general |
ease-in | Empieza lento, termina rápido | Elementos que salen de pantalla |
ease-out | Empieza rápido, termina lento | Elementos que entran; hover |
ease-in-out | Lento al inicio y al final | Movimientos de ida y vuelta |
cubic-bezier(x1, y1, x2, y2) | Curva personalizada | Efectos con rebote o carácter |
steps(n) | Saltos discretos | Animaciones tipo sprite o máquina de escribir |
Compáralas en vivo:
css
/* Rebote sutil al final (sobrepasa el destino y regresa) */
.modal {
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}Puedes diseñar curvas visualmente en las DevTools (clic en el ícono de curva junto al valor) o en cubic-bezier.com.
Duraciones recomendadas
- Micro-interacciones (hover, foco, botones): 150–250 ms.
- Elementos medianos (menús, tooltips, acordeones): 250–400 ms.
- Elementos grandes (modales, paneles laterales): 300–500 ms.
Más de 500 ms se siente lento. La interfaz debe sentirse ágil.
¿Qué propiedades se pueden animar?
Las que tienen valores intermedios: colores, medidas, opacity, transform, sombras... No se animan las discretas como display o font-family (aunque los navegadores modernos permiten transition-behavior: allow-discrete para display en casos específicos).
Rendimiento: anima transform y opacity
Cuando cambias width, height, top, left o margin, el navegador debe recalcular el layout de la página (reflow) en cada fotograma: puede ir a tirones, sobre todo en móviles. En cambio, transform y opacity se procesan en la tarjeta gráfica (GPU), sin recalcular el layout.
css
/* ❌ Costoso: recalcula el layout en cada fotograma */
.tarjeta:hover { top: -8px; width: 105%; }
/* ✅ Eficiente: sólo se recompone en la GPU */
.tarjeta:hover { transform: translateY(-8px) scale(1.05); }will-change
Para animaciones complejas puedes avisar al navegador con will-change: transform;. Úsalo con moderación y sólo en los elementos que realmente se animan: abusar de él consume memoria.
Transformaciones 2D
css
.elemento {
transform: translate(20px, 10px); /* mueve en X e Y */
transform: translateX(-50%); /* % relativo al propio elemento */
transform: rotate(45deg); /* gira (deg, turn: 0.5turn = 180deg) */
transform: scale(1.2); /* agranda 20 % */
transform: scale(1, 0.5); /* X, Y por separado */
transform: skew(10deg, 0); /* inclina */
/* Varias a la vez: se aplican de DERECHA a IZQUIERDA */
transform: translateY(-4px) rotate(3deg) scale(1.05);
}El orden importa
rotate(45deg) translateX(100px) no es lo mismo que translateX(100px) rotate(45deg). En el primero, el elemento se mueve en la dirección ya girada. Además, declarar transform en :hover reemplaza el transform del estado base: incluye todas las funciones que necesites.
Propiedades individuales: translate, rotate, scale
Los navegadores modernos permiten usarlas por separado, lo que evita el problema anterior:
css
.icono {
translate: 0 0;
rotate: 0deg;
scale: 1;
transition: translate 0.2s, rotate 0.3s, scale 0.2s;
}
.boton:hover .icono {
translate: 4px 0; /* sólo cambia el desplazamiento, sin perder la rotación */
}
.boton.abierto .icono {
rotate: 180deg;
}transform-origin
El punto sobre el cual ocurre la transformación (por defecto, el centro):
css
.puerta {
transform-origin: left center; /* gira como una puerta desde su bisagra */
transition: transform 0.4s;
}
.puerta:hover {
transform: rotateY(-30deg);
}
.subrayado {
transform-origin: left; /* crece de izquierda a derecha */
transform: scaleX(0);
}Transformaciones 3D
css
.escena {
perspective: 800px; /* distancia del "ojo": menor = más dramático */
}
.objeto {
transform: rotateX(20deg); /* gira sobre el eje horizontal */
transform: rotateY(45deg); /* gira sobre el eje vertical */
transform: translateZ(50px); /* acerca al espectador */
}Juega con todas las transformaciones y prueba la tarjeta volteable:
Tarjeta volteable (flip card)
html
<div class="flip" tabindex="0">
<div class="flip__inner">
<div class="flip__cara flip__frente">Plan Básico</div>
<div class="flip__cara flip__reverso">$299/mes · 5 páginas · Hosting incluido</div>
</div>
</div>css
.flip {
width: 280px;
height: 180px;
perspective: 1000px; /* en el padre: profundidad */
}
.flip__inner {
position: relative;
height: 100%;
transition: transform 0.7s;
transform-style: preserve-3d; /* los hijos conservan su posición 3D */
}
.flip:hover .flip__inner,
.flip:focus-within .flip__inner { /* también con teclado */
transform: rotateY(180deg);
}
.flip__cara {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 12px;
backface-visibility: hidden; /* oculta la cara cuando está de espaldas */
}
.flip__reverso {
transform: rotateY(180deg); /* empieza volteada */
}Casos de uso reales
css
/* 1. Tarjeta que se eleva al pasar el mouse */
.tarjeta {
transition: transform 0.25s ease-out, box-shadow 0.25s ease-out;
}
.tarjeta:hover {
transform: translateY(-6px);
box-shadow: 0 16px 32px rgb(0 0 0 / 0.12);
}
/* 2. Zoom de imagen dentro de un marco */
.marco { overflow: hidden; border-radius: 12px; }
.marco img { transition: transform 0.5s ease; }
.marco:hover img { transform: scale(1.08); }
/* 3. Botón que se "hunde" al presionarlo */
.boton:active { transform: scale(0.97); }
/* 4. Flecha que gira al abrir un acordeón */
details summary::after {
content: "▾";
display: inline-block;
transition: rotate 0.3s;
}
details[open] summary::after { rotate: 180deg; }
/* 5. Menú móvil que entra desde la derecha */
.menu-movil {
position: fixed;
inset: 0 0 0 auto;
width: min(80%, 320px);
transform: translateX(100%); /* fuera de la pantalla */
transition: transform 0.3s ease-out;
}
.menu-movil.abierto {
transform: translateX(0);
}Respeta a quien prefiere menos movimiento
css
@media (prefers-reduced-motion: reduce) {
* {
transition-duration: 0.01ms !important;
}
}Ejercicios
- Haz que un botón cambie de color suavemente en 200 ms al pasar el mouse.
- Crea una tarjeta que se eleve 8px y aumente su sombra en hover, usando sólo
transformybox-shadow. - Haz que un ícono de flecha
→se desplace 4px a la derecha cuando se pasa el mouse sobre su enlace. - Crea un subrayado que crezca desde el centro al pasar el mouse sobre un enlace.
Ver solución
css
/* 1 */
.boton {
background: #2563eb;
transition: background-color 0.2s ease;
}
.boton:hover { background: #1d4ed8; }
/* 2 */
.tarjeta {
box-shadow: 0 2px 6px rgb(0 0 0 / 0.08);
transition: transform 0.25s ease-out, box-shadow 0.25s ease-out;
}
.tarjeta:hover {
transform: translateY(-8px);
box-shadow: 0 16px 30px rgb(0 0 0 / 0.15);
}
/* 3 */
.enlace .flecha {
display: inline-block; /* transform no funciona en elementos inline */
transition: translate 0.2s ease-out;
}
.enlace:hover .flecha { translate: 4px 0; }
/* 4 */
.enlace-sub {
position: relative;
text-decoration: none;
}
.enlace-sub::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -3px;
height: 2px;
background: currentColor;
transform: scaleX(0); /* transform-origin por defecto: centro */
transition: transform 0.3s ease-out;
}
.enlace-sub:hover::after { transform: scaleX(1); }Resumen
transitionse declara en el estado base: propiedad, duración, función de tiempo y retraso.- Lista las propiedades en lugar de
all; 150–400 ms es la duración ideal. ease-outpara entradas y hover;ease-inpara salidas;cubic-bezierpara personalidad.- Anima
transformyopacity: son eficientes; evita animarwidth,topomargin. transformcombinatranslate,rotate,scaleyskew(el orden importa); las propiedades individuales evitan conflictos.- 3D:
perspectiveen el padre,transform-style: preserve-3dybackface-visibility: hidden. transformno funciona en elementosinline: usainline-block.
Siguiente: Animaciones con @keyframes.