Skip to content

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ónComportamientoIdeal para
linearVelocidad constanteRotaciones infinitas, barras de progreso
easeArranca rápido, frena al final (por defecto)Uso general
ease-inEmpieza lento, termina rápidoElementos que salen de pantalla
ease-outEmpieza rápido, termina lentoElementos que entran; hover
ease-in-outLento al inicio y al finalMovimientos de ida y vuelta
cubic-bezier(x1, y1, x2, y2)Curva personalizadaEfectos con rebote o carácter
steps(n)Saltos discretosAnimaciones tipo sprite o máquina de escribir

Compáralas en vivo:

Funciones de tiempo comparadas
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:

Transformaciones 2D y 3D

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 ​

  1. Haz que un botón cambie de color suavemente en 200 ms al pasar el mouse.
  2. Crea una tarjeta que se eleve 8px y aumente su sombra en hover, usando sólo transform y box-shadow.
  3. Haz que un ícono de flecha → se desplace 4px a la derecha cuando se pasa el mouse sobre su enlace.
  4. 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 ​

  • transition se 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-out para entradas y hover; ease-in para salidas; cubic-bezier para personalidad.
  • Anima transform y opacity: son eficientes; evita animar width, top o margin.
  • transform combina translate, rotate, scale y skew (el orden importa); las propiedades individuales evitan conflictos.
  • 3D: perspective en el padre, transform-style: preserve-3d y backface-visibility: hidden.
  • transform no funciona en elementos inline: usa inline-block.

Siguiente: Animaciones con @keyframes.

Academia Web — aprende, copia y construye.