Skip to content
This page has been auto-translated and may contain errors.View in English

Transiciones y animaciones

docs.scrimba.com

Añades un color :hover a un botón y funciona, pero el cambio es abrupto: un fotograma está azul, el siguiente está más oscuro, sin nada en medio. Un pequeño desvanecimiento haría que toda la página se sintiera menos entrecortada. Ese desvanecimiento es una transición, y una vez que puedes describir movimiento entre dos estados, las mismas herramientas escalan hasta efectos hover, tarjetas que se elevan, y un spinner que gira mientras algo se carga. Este capítulo trata sobre cómo CSS mueve cosas, y cómo moverlas sin hacer la página más difícil de usar.

Transiciones

Una transición le dice al navegador que anime una propiedad de su valor anterior al nuevo en lugar de cambiar entre ellos bruscamente. Describes dos estados, el normal y el hover, y la transición rellena los fotogramas en medio.

Aquí hay un botón que cambia de color al pasar el mouse. Sin una transición el color salta; con una se desvanece:

css
.btn {
  background: #3b82f6;
  transition: background 0.2s;   /* desvanece el color en 0.2 segundos */
}
.btn:hover {
  background: #1d4ed8;           /* el nuevo estado hacia el que animar */
}

Pones transition en el estado normal, no en :hover. De esa manera se aplica tanto cuando llega el mouse como cuando se va.

Una transición anima una propiedad entre dos valores siempre que ese valor cambie, generalmente porque entra en juego un estado como :hover o :focus, o se añade una clase. No estás describiendo la animación fotograma por fotograma; nombras la propiedad y una duración, y el navegador interpola los valores en medio.

La abreviatura transition tiene cuatro partes, en este orden: la propiedad a observar, la duración, la timing-function (la curva de velocidad), y un delay opcional.

css
.btn {
  background: #3b82f6;
  transition: background 0.2s ease-in-out 0s;
  /* propiedad | duración | timing-function | delay */
}
.btn:hover {
  background: #1d4ed8;
}

Puedes hacer una transición de varias propiedades a la vez separándolas con comas, y declaras la transición en el estado base para que cubra tanto entrar como salir del hover.

css
.card {
  transition: transform 0.2s, box-shadow 0.2s;   /* dos propiedades, mismo timing */
}

Una transición interpola una propiedad entre su valor inicial y final a lo largo del tiempo siempre que el valor calculado cambie. El disparador es cualquier cambio en esa propiedad: una pseudo-clase como :hover, una clase que se alterna desde JavaScript, o una edición de estilo inline. Las cuatro sub-propiedades son transition-property, transition-duration, transition-timing-function, y transition-delay, y la abreviatura las ordena: propiedad, duración, timing, delay.

Dos restricciones deciden si una transición se ejecuta en absoluto. La propiedad tiene que ser animable, lo que significa que tiene una interpolación definida entre dos valores: background-color, opacity, y transform lo son, mientras que una propiedad como display es discreta e históricamente cambiaba bruscamente en lugar de suavizar. Y ambos valores, inicial y final, tienen que ser concretos: no puedes hacer una transición hacia o desde auto, que es por qué animar height a auto no hace nada y la gente recurre a max-height o transform en su lugar.

css
.btn {
  background: #3b82f6;
  transition: background-color 0.2s ease-in-out;   /* animable, ambos valores concretos */
}
.btn:hover {
  background: #1d4ed8;
}

Declara la transición en la regla base, no en la regla :hover. La propiedad de transición en vigor se lee desde el estado actual del elemento, así que ponerla en el estado base significa que rige tanto la llegada como el retorno; ponerla solo en :hover anima la llegada y hace snap la salida.

JunoTransiciones Una transición suaviza un cambio en lugar de hacer snap, como desvaneciendo el color de un botón al pasar el mouse. La escribes en el estado normal, no en :hover, así que funciona entrando y saliendo. Comienza con property y una duration y ya tienes un desvanecimiento funcional.
JunoTransiciones La abreviatura transition lee propiedad, duración, timing-function, delay, y listas varias separándolas con comas. Ponla en la regla base para que cubra entrar y salir del estado. Si un hover se ve suave entrando y salta saliendo, la transición está en :hover en lugar de la base, lo que atrapa a la gente a menudo.
JunoTransiciones Una transición solo se ejecuta en propiedades animables con dos valores finales concretos, que es por qué hacer una transición a height: auto silenciosamente no hace nada. Mantén la declaración en el estado base para que rija ambas direcciones en lugar de hacer snap en la salida. Una vez que conoces las cuatro sub-propiedades, la abreviatura es solo su orden: propiedad, duración, timing, delay.

Transformaciones

Una transformación mueve, escala o rota un elemento sin perturbar nada a su alrededor. Es lo que normalmente querrás animar, porque se ve suave y no empuja a los vecinos fuera del camino.

Hay tres que usarás constantemente:

css
.card:hover {
  transform: translateY(-4px);   /* mueve hacia arriba 4 píxeles */
}
.icon:hover {
  transform: scale(1.1);         /* crece al 110% de su tamaño */
}
.arrow.open {
  transform: rotate(90deg);      /* gira un cuarto de vuelta en sentido horario */
}

translate mueve, scale redimensiona, y rotate gira. Combina cualquiera de estos con una transición y obtienes una tarjeta que se eleva, un ícono que salta, o una flecha que se abre girando.

transform aplica un cambio visual a un elemento, moviéndolo, redimensionándolo o girándolo, sin cambiar su lugar en el diseño. Esa última parte es el punto: un elemento transformado se pinta en una nueva posición, pero el espacio que originalmente ocupaba permanece reservado, por lo que sus vecinos no se redistribuyen. Esta es la razón por la que transform es la propiedad que animas para movimiento en lugar de top o margin.

Los tres que usas más son translate(), scale(), y rotate(), y puedes combinarlos en una declaración:

css
.card {
  transition: transform 0.2s ease-out;
}
.card:hover {
  transform: translateY(-4px) scale(1.02);   /* eleva y crece ligeramente */
}

Las funciones se aplican de izquierda a derecha, y transform-origin establece el punto alrededor del que giran, que por defecto es el centro del elemento. Cámbialo a top left y un rotate gira desde la esquina en lugar del centro.

css
.badge {
  transform-origin: top left;
  transform: rotate(-8deg);
}

transform reposiciona un elemento en su propia capa pintada sin afectar el flujo del documento: el espacio original del cuadro permanece reservado, así que ningún hermano se redistribuye y ningún diseño se recalcula cuando la transformación cambia. Esa propiedad es lo que hace que transform sea lo correcto para animar, un punto que la sección Deep Dive sobre rendimiento a continuación hace preciso.

translate(), scale(), y rotate() son las funciones comunes, y el orden es significativo porque se componen como multiplicación de matrices aplicadas de derecha a izquierda en efecto: translateX(50px) rotate(45deg) mueve primero y luego rota el elemento movido, mientras que lo inverso rota primero y luego traduce a lo largo de los ejes rotados, dando un resultado diferente. transform-origin establece el pivote para scale y rotate, por defecto al centro del elemento (50% 50%).

css
.panel {
  transform-origin: top center;
  transition: transform 0.25s ease-out;
}
.panel.collapsed {
  transform: scaleY(0);          /* colapsa hacia el borde superior */
}

Una restricción práctica: transform solo se aplica a elementos que generan un cuadro que puedas posicionar de esta manera, así que no tiene efecto en elementos inline como un <span> bare. Dale al elemento display: inline-block o block primero. Si también estás posicionando el elemento, nota cómo las transformaciones interactúan con contextos de posicionamiento, ya que un elemento transformado se convierte en un bloque contenedor para sus descendientes posicionados absolutamente.

JunoTransformacionestransform mueve, escala o rota un elemento sin empujar a sus vecinos. Los tres a recordar son translate para mover, scale para redimensionar, y rotate para girar. Combínalo con una transición y obtienes una tarjeta que se eleva o un ícono que salta al pasar el mouse.
JunoTransformacionestransform cambia cómo se ve un elemento sin cambiar dónde se sienta en el diseño, así que los vecinos nunca se redistribuyen, que es por qué lo animas en lugar de top o margin. Apila translate, scale, y rotate en una declaración y se aplican de izquierda a derecha. Recurre a transform-origin cuando quieras que un rotate o scale gire alrededor de algo que no sea el centro.
JunoTransformaciones Las funciones de transformación se componen en orden, así que translateX(50px) rotate(45deg) no es lo mismo que lo inverso, y transform-origin establece alrededor de qué pivota un rotate o scale. No hace nada en un elemento inline bare hasta que lo haces inline-block. Ten en cuenta que también convierte el elemento en un bloque contenedor para hijos posicionados absolutamente, lo que puede sorprenderte después.

Animaciones de fotogramas clave

Una transición anima entre dos estados. Cuando necesitas más de dos, o necesitas que el movimiento se repita, usas una animación de fotogramas clave. Escribes los pasos con @keyframes, luego los adjuntas a un elemento con la propiedad animation.

Un spinner de carga es el ejemplo clásico: gira para siempre, así que una transición no puede hacerlo.

css
@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.spinner {
  animation: spin 1s linear infinite;   /* ejecuta spin, 1s por vuelta, para siempre */
}

from es el inicio y to es el final. La línea animation dice qué fotogramas clave ejecutar, cuánto tiempo tarda un ciclo, la curva de velocidad, y cuántas veces repetir. infinite significa que nunca se detiene.

@keyframes define una secuencia nombrada de pasos, y la propiedad animation la ejecuta en un elemento. A diferencia de una transición, una animación no necesita un cambio de estado para activarse, comienza por sí sola, y puede tener tantos pasos como quieras, usando porcentajes de 0% a 100%.

css
@keyframes pulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.05); }   /* el paso del punto medio */
  100% { transform: scale(1); }
}
.badge {
  animation: pulse 1.5s ease-in-out infinite;
}

La abreviatura animation agrupa varias sub-propiedades: nombre, duración, timing-function, delay, iteration-count, y direction. infinite repite para siempre, un número ejecuta esa cantidad de veces, y alternate como dirección hace que cada ciclo se invierta para que el movimiento vaya hacia adelante y hacia atrás en lugar de saltar al inicio.

css
.spinner {
  animation: spin 1s linear infinite;
}

@keyframes declara una línea de tiempo nombrada de valores de propiedad en puntos expresados como porcentajes (from y to son alias para 0% y 100%), y animation vincula esa línea de tiempo a un elemento. Una animación se activa por sí sola y puede repetirse, que es la línea entre ella y una transición: usa una transición para suavizar un cambio de estado, usa una animación para movimiento que se ejecuta por sí solo o se repite.

La abreviatura animation lleva ocho sub-propiedades: animation-name, -duration, -timing-function, -delay, -iteration-count, -direction, -fill-mode, y -play-state. Dos de ellas atrapan a la gente. animation-fill-mode: forwards hace que el elemento mantenga su fotograma clave final después de que termina la animación, en lugar de hacer snap a su estilo sin animar, que es lo que quieres para una entrada de una sola vez. Y la timing-function se aplica por segmento de fotograma clave, no a lo largo de toda la animación, así que ease-in-out en una animación de tres pasos suaviza dentro y fuera de cada segmento, no una sola vez de principio a fin.

css
@keyframes slide-in {
  from { opacity: 0; transform: translateX(-16px); }
  to   { opacity: 1; transform: translateX(0); }
}
.toast {
  animation: slide-in 0.3s ease-out forwards;   /* mantiene el estado final */
}

Prefiere animar transform y opacity dentro de fotogramas clave por la misma razón que las transiciones lo hacen, que la próxima sección desempaqueta: esas dos propiedades son baratas de animar y la mayoría de las otras no lo son.

JunoAnimaciones de fotogramas clave Cuando dos estados no son suficientes, o el movimiento necesita repetirse, escribe los pasos en @keyframes y ejecútalos con animation. Un spinner es el ejemplo de referencia: from a 0 grados, to a 360, repitiendo con infinite. La línea animation establece qué fotogramas, cuánto tiempo, la curva, y cuántas veces.
JunoAnimaciones de fotogramas clave@keyframes contiene los pasos como porcentajes y animation los ejecuta, sin cambio de estado necesario. La abreviatura empaqueta nombre, duración, timing, delay, iteration-count, y direction, así que infinite repite y alternate hace que cada ciclo se ejecute hacia adelante y hacia atrás. El movimiento multi-paso y cualquier cosa que se repita pertenece aquí, no en una transición.
JunoAnimaciones de fotogramas clave La timing-function se ejecuta por segmento de fotograma clave, no una sola vez a lo largo de toda la animación, lo que sorprende a la gente la primera vez que una ejecución multi-paso alisada se ve irregular. Recurre a animation-fill-mode: forwards cuando una entrada de una sola vez debe mantener su estado final en lugar de hacer snap atrás. Y mantén los fotogramas en transform y opacity donde puedas, por la razón de rendimiento que viene a continuación.

Movimiento que respeta al usuario

No todos quieren movimiento en la pantalla. A algunas personas les resulta distractor, y para algunas puede causar incomodidad real o náusea. Los navegadores dejan que las personas pidan menos en su configuración del sistema, y CSS puede leer esa solicitud con prefers-reduced-motion.

Envuelve tus animaciones en una verificación para que solo se ejecuten para personas que no han pedido reducir el movimiento:

css
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.2s;
  }
}

La otra mitad de respetar al usuario es la contención: mantén el movimiento corto, sutil, y atado a algo que el lector hizo, como un hover o un clic. El movimiento que llama la atención a un cambio real ayuda; el movimiento por su propia cuenta se cansa rápido.

El movimiento no es neutral. Para algunos lectores, la animación es una distracción, y para las personas con condiciones vestibulares, el movimiento grande o repentino puede desencadenar mareos o náusea. Los sistemas operativos exponen una configuración de "reducir movimiento", y la característica de media prefers-reduced-motion te permite que tu CSS la honre.

Trata el movimiento como opt-in, no opt-out. Escribe la línea base tranquila como tu valor predeterminado, luego capa el movimiento dentro de una consulta no-preference:

css
.card {
  transition: none;              /* línea base tranquila para todos */
}
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.2s ease-out;
  }
}

Más allá de la consulta de media, el propósito es lo que separa el buen movimiento del ruido. El movimiento se gana su lugar cuando explica un cambio: un menú que se desliza desde el borde al que está anclado, un toast que se desvanece para que no aparezca de la nada. Rápido y sutil se lee como pulido; lento y grande se lee como un obstáculo.

prefers-reduced-motion es una característica de media que refleja una configuración de accesibilidad que el lector eligió a nivel del SO, y honrarla es un requisito base, no una gentileza, porque el movimiento involuntario puede causar incomodidad vestibular. El patrón a usar es escribir el estado reducido como el predeterminado y habilitar movimiento dentro de @media (prefers-reduced-motion: no-preference), por lo que un lector cuyo navegador no soporta la característica aún obtiene la línea base tranquila en lugar del movimiento.

"Reducir" rara vez significa "eliminar". Un desvanecimiento o un cambio de color generalmente está bien cuando un deslizamiento o una escala no lo es, así que el movimiento útil es intercambiar movimiento posicional grande por un pequeño cambio de opacidad en lugar de matar toda la retroalimentación:

css
.modal {
  animation: slide-in 0.3s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .modal {
    animation: fade-in 0.3s ease-out;   /* mantén retroalimentación, suelta el movimiento */
  }
}

El propósito y el suavizado llevan el resto. Las entradas se leen mejor con ease-out, que comienza rápido y se asienta, porque el elemento llega con intención y viene a descansar; las salidas a menudo se adaptan a ease-in. Mantén duraciones cortas (aproximadamente 150ms a 300ms para retroalimentación de interfaz) y movimiento pequeño; un fracaso común es una animación hermosa que añade un cuarto de segundo de espera a cada interacción. El movimiento debe aclarar un cambio de estado, no decorar uno.

JunoMovimiento que respeta al usuario A algunas personas les resulta el movimiento distractor o incluso físicamente incómodo, y pueden pedir al navegador menos de él. Lee eso con prefers-reduced-motion y solo ejecuta animaciones para personas que no han reducido el movimiento. Mantén el movimiento que sí añades corto, sutil, y atado a algo que el lector hizo.
JunoMovimiento que respeta al usuario Escribe la versión tranquila como tu predeterminado y añade movimiento dentro de una consulta no-preference, así el movimiento es opt-in en lugar de algo que la gente tiene que luchar contra. El buen movimiento explica un cambio, como un menú deslizándose desde su ancla; el movimiento para decoración se cansa rápido. Rápido y pequeño se lee como pulido, lento y grande se lee como un obstáculo.
JunoMovimiento que respeta al usuario Reducir rara vez significa eliminar: intercambia un deslizamiento por un desvanecimiento para que la gente que pidió menos movimiento aún obtenga retroalimentación, y usa por defecto el estado tranquilo para que navegadores no soportados caigan allí también. Usa ease-out para entradas y mantén la retroalimentación de interfaz alrededor de 150 a 300 milisegundos. Una animación hermosa que añade una espera a cada clic es un costo, no una característica.

Qué animar, y por qué importa

Una regla rápida que te ahorrará problemas después: cuando animes movimiento, anima transform y opacity, no propiedades como width, height, top, o margin.

css
/* suave: el navegador maneja estos barato */
.card:hover {
  transform: translateY(-4px);
  opacity: 0.9;
}

La razón es que transform y opacity son las dos propiedades que los navegadores pueden mover más barato, así que las animaciones construidas sobre ellas permanecen suaves incluso en dispositivos más lentos. Animar propiedades de tamaño o posición hace que el navegador haga más trabajo cada fotograma, y el movimiento puede entrecortarse. Te encontrarás con la razón completa cuando profundices, pero el hábito vale la pena construirlo ahora.

Hay una razón por la que los ejemplos en este capítulo se inclinan sobre transform y opacity. Esas dos propiedades son lo más barato para animar, y la mayoría de las otras no lo son. Animar width, height, top, o margin obliga al navegador a recalcular el diseño en cada fotograma, lo que es costoso y se muestra como entrecortado en dispositivos más lentos.

La regla práctica es expresar movimiento como transform y opacity donde sea posible. ¿Quieres mover algo? Usa translate, no top. ¿Quieres redimensionarlo? Usa scale, no width. ¿Quieres que aparezca? Desvanece opacity, no animes height desde cero.

css
/* costoso: animar una propiedad de diseño */
.panel {
  transition: height 0.3s;       /* recalcula el diseño cada fotograma */
}

/* barato: mismo efecto, expresado como transform */
.panel {
  transition: transform 0.3s;
  transform: scaleY(0);          /* sin trabajo de diseño */
}

Esto se conecta de vuelta a por qué transform no perturba a sus vecinos: porque nunca toca el diseño, el navegador puede animarlo sin rehacer la página. Si te encuentras recurriendo también a una transición de color o fondo, colores y fondos cubre cuáles de esos se interpolan.

Este es el Deep Dive que las secciones anteriores señalaban: por qué transform y opacity son baratos para animar y casi todo lo demás no lo es. Se reduce al pipeline de renderizado del navegador, que se ejecuta en tres etapas. Layout calcula el tamaño y posición de cada cuadro. Paint rellena píxeles: colores, texto, bordes. Composite ensambla las capas pintadas en el fotograma final, y es la etapa que la GPU puede dirigir.

Animar transform u opacity puede hacerse enteramente en la etapa de composite. El navegador pinta el elemento una vez, lo promueve a su propia capa del compositor (una superficie pintada por separado que la GPU puede mover y mezclar independientemente), y luego cada fotograma solo recomposita esa capa en una nueva posición u opacidad. Sin diseño, sin repintado. Animar width o top en su lugar invalida el diseño, obligando al navegador a recalcular la geometría y repintar en cada fotograma, que es lo que deja caer fotogramas bajo carga.

css
/* solo composite: barato, amigable con GPU */
.card {
  transition: transform 0.2s, opacity 0.2s;
}

/* dispara diseño y pintado cada fotograma: evita animar estos */
.card-heavy {
  transition: width 0.2s, top 0.2s;
}

will-change te deja insinuar que una propiedad está a punto de animarse para que el navegador pueda promover la capa por adelantado: will-change: transform. Úsalo raramente y elimínalo cuando la animación termina, porque cada capa promovida cuesta memoria, y promover todo derrota el propósito y puede ralentizar la página. Trátalo como una corrección específica para un entrecortado medido, no como un predeterminado.

El suavizado es la otra palanca que vale la pena nombrar con precisión. La timing-function es una curva que mapea el tiempo transcurrido al progreso. ease-out (rápido luego ralentizando) se adapta a entradas porque el elemento llega y se asienta; ease-in se adapta a salidas; linear se adapta a movimiento continuo como un spinner. Para cualquier cosa personalizada, cubic-bezier(x1, y1, x2, y2) define la curva por sus dos puntos de control, y las palabras clave incorporadas son cubic-beziers nombrados por debajo. Mantén un presupuesto de rendimiento en mente: apunta a mantenerte dentro del tiempo de un fotograma (aproximadamente 16ms a 60fps), anima solo propiedades de composite, y recurre al perfilador antes de recurrir a will-change. Si ejecutas varios de estos desde propiedades personalizadas, puedes mantener duraciones y suavizado consistentes en toda una interfaz.

JunoQué animar, y por qué importa Anima transform y opacity, y aléjate de animar width, height, top, o margin. Esos dos son los más baratos para que el navegador mueva, así que el movimiento permanece suave incluso en dispositivos más lentos. Aprenderás el por qué completo después, pero el hábito vale la pena comenzar ahora.
JunoQué animar, y por qué importa Expresa movimiento como transform y opacity: usa translate en lugar de top, scale en lugar de width, y desvanece opacity en lugar de animar height desde cero. Animar propiedades de diseño hace que el navegador rehaga el diseño cada fotograma, lo que se entrecorta en dispositivos más lentos. Es la misma razón por la que transform deja a los vecinos en paz: nunca toca el diseño.
JunoQué animar, y por qué importatransform y opacity pueden animar en la etapa de composite en su propia capa de GPU, así que saltan diseño y pintado; width y top invalidan el diseño cada fotograma y pierden fotogramas bajo carga. Usa will-change como una corrección específica para un entrecortado medido y elimínalo después, ya que cada capa promovida cuesta memoria. Elige suavizado para que coincida con el gesto, ease-out para entradas, y perfila antes de optimizar.