Transiciones

Qué es una transición

Una transición es el camino entre dos estados. Hasta ahora, cuando una regla de CSS cambiaba el aspecto de un elemento —un :hover que cambia el color de fondo—, el cambio era instantáneo: un fotograma con el color viejo y el siguiente con el nuevo.

Con una transición se le dice al navegador: «no salte ; tarda medio segundo en ir de un valor a otro». El navegador calcula él solo todos los valores intermedios y los va dibujando. No hay que escribir ninguno.

Una transición no es una animación. Una transición solo sabe ir de A a B, y necesita que algo provoque ese cambio. Una animación tiene un guion propio, con las etapas que se quieran, y arranca sola. Eso se verá en el documento siguiente.

Los disparadores: qué cambia el estado

Esta es la parte que se olvida. Una transición no hace nada por sí misma: es una instrucción en espera. Hasta que no hay un cambio de estado no se ve absolutamente nada.

Y los estados ya los conocen: son las pseudoclases de la clase anterior, más dos nuevas.

PseudoclaseQué la activaQuién la activa
:hoverel ratón está encimael ratón
:focusel elemento tiene el focoel tabulador o un clic dentro
:activese está pulsandoel botón del ratón, sin soltar
:checkeduna casilla o un radio está marcadoun clic del usuario
:targetla dirección de la página acaba en el id de ese elementoun enlace con #

Las tres primeras duran lo que dura el gesto. Las dos últimas se quedan: una casilla marcada sigue marcada, y :target aguanta hasta que se pincha otro enlace. Eso permite construir cosas que se abren y se quedan abiertas sin una línea de JavaScript.

El caso más sencillo

Dos reglas: el estado de partida y el de llegada. Y en el de partida, la transición.

transition: background-color 500ms — pasa el ratón por la caja del medio

<div class="row">
  <div class="caja">1</div>
  <div class="caja marcada">2</div>
  <div class="caja">3</div>
</div>

Y lo mismo sin la línea de transition sería el salto brusco de siempre. La transición es una sola línea añadida: no cambia qué pasa, cambia cómo pasa.

El disparador puede estar en otro elemento

No hace falta que el ratón entre en la caja que se mueve. Basta con que el selector llegue hasta ella: aquí el :hover está en la fila, y lo que se mueve es la caja del medio.

el ratón entra en la fila, se mueve la caja del medio

<div class="row">
  <div class="caja">1</div>
  <div class="caja marcada">2</div>
  <div class="caja">3</div>
</div>

El código lleva dos colores, y son los mismos en todos los ejemplos que siguen:

Las dos se escriben en reglas distintas, y ahí está lo que despista. El cambio va en la regla del :hover, porque solo ocurre al pasar el ratón. La transición va en la regla de reposo, en .marcada, porque el elemento la tiene siempre: así se aplica tanto al entrar el ratón como al salir. Si se escribiera dentro del :hover, la caja iría despacio al entrar y volvería de golpe al salir, porque al salir ya no hay ninguna transición declarada.

El orden en que aparezcan en la hoja no importa; lo que importa es en qué regla está cada una.

Esto es exactamente el mecanismo del menú desplegable: el ratón entra en la opción y lo que cambia es el submenú. Con el combinador + se puede llegar incluso al hermano siguiente: .caja:hover + .caja afecta a la caja de al lado.

Las cuatro propiedades

Una transición se describe con cuatro datos: qué propiedad, cuánto tarda, con qué ritmo y cuándo empieza. Hay una propiedad para cada uno, y una abreviatura que las reúne.

transition-property: qué se transiciona

El nombre de la propiedad que debe ir suave. Lo que no esté en esa lista sigue saltando, y eso se puede usar a propósito. Aquí cambian dos cosas con el ratón, pero solo una está en la lista:

transition-property: transform — el giro es suave, el color salta

<div class="row">
  <div class="caja">1</div>
  <div class="caja marcada">2</div>
  <div class="caja">3</div>
</div>

Con varias propiedades se separan por comas: transition-property: transform, background-color; Y con la palabra all se incluyen todas las que cambien, que es cómodo mientras se aprende pero obliga al navegador a vigilarlas todas.

transition-duration: cuánto tarda

El único dato obligatorio: sin duración, la transición vale cero segundos y no se ve. Se escribe en segundos (0.4s) o en milisegundos (400ms), que es lo habitual porque se lee mejor.

tres duraciones para el mismo recorrido

<div class="column">
  <div class="caja d1">100 ms</div>
  <div class="caja d2">600 ms</div>
  <div class="caja d3">2 s</div>
</div>

El criterio práctico: entre 150 y 400 ms para cambios pequeños (colores, bordes, botones). Por debajo de 100 ms no se percibe como movimiento, y por encima de 600 ms la interfaz se siente lenta y molesta. Los dos segundos del ejemplo son para verlo, no para usarlo.

transition-timing-function: con qué ritmo

Un recorrido de 600 ms puede hacerse a velocidad constante, o arrancando despacio, o frenando al final. Eso es la curva, y es lo que separa un movimiento que parece natural de uno que parece mecánico.

las cuatro curvas habituales, el mismo recorrido y la misma duración

<div class="column">
  <div class="caja c1">ease</div>
  <div class="caja c2">linear</div>
  <div class="caja c3">ease-in</div>
  <div class="caja c4">ease-out</div>
</div>
ease
El valor por defecto: arranca algo acelerado y frena al llegar. Si no se escribe nada, es esto lo que se obtiene.
linear
Velocidad constante de principio a fin. Suena «correcto» pero se nota artificial: en el mundo real nada arranca y se detiene de golpe. Sirve para colores y para giros continuos.
ease-in
Arranca despacio y acaba rápido. Deja la sensación de que algo se va: es la curva de lo que desaparece.
ease-out
Arranca rápido y frena al final. La mejor para lo que aparece o responde a un clic, porque el usuario ve el resultado enseguida y solo el final es suave.
ease-in-out
Despacio en los dos extremos, rápido en medio. Para recorridos largos.
cubic-bezier(...) y steps(...)
Las dos formas generales. cubic-bezier define la curva mediante cuatro números; las cinco anteriores son casos particulares suyos, y es la vía para obtener un rebote. steps divide el recorrido en saltos discretos.

transition-delay: cuándo empieza

Lo que se espera antes de arrancar. Con el mismo valor en todas no sirve de gran cosa; su uso de verdad es escalonar varios elementos para que entren uno detrás de otro.

la misma transición, tres retardos distintos

<div class="column">
  <div class="caja r1">0 ms</div>
  <div class="caja r2">150 ms</div>
  <div class="caja r3">300 ms</div>
</div>

Un retardo negativo es legal y significa «empieza ya, pero por la mitad»: transition-delay: -200ms en una transición de 400 ms la arranca a medio camino.

La abreviatura

Las cuatro en una sola línea. El orden de los dos tiempos es lo único que hay que recordar: primero la duración, después el retardo.

transition: <propiedad> <duración> <curva> <retardo>;

transition: transform 400ms ease-out 100ms;

Se pueden omitir la curva y el retardo, y queda transition: transform 400ms;, que es la forma que se usa el 90 % de las veces. Para varias propiedades con tiempos distintos, se separan por comas:

dos propiedades con tiempos distintos en la misma declaración

<div class="row">
  <div class="caja">1</div>
  <div class="caja marcada">2</div>
  <div class="caja">3</div>
</div>

La caja crece enseguida y el color va llegando después. Cada propiedad lleva su propio juego de cuatro datos.

Dónde se escribe: el error que todos cometen

La transición va en el estado normal, no dentro del :hover. Es el fallo más repetido del tema, y lo peor es que parece funcionar. En el ejemplo están las dos versiones: pasa el ratón por cada caja y, sobre todo, sácalo.

la de la izquierda es correcta; la de la derecha vuelve de golpe

<div class="row">
  <div class="caja bien">bien</div>
  <div class="caja mal">mal</div>
</div>

La razón es que la regla de :hover solo existe mientras el ratón está encima. Al entrar, hay transición. Al salir, el :hover desaparece y con él la instrucción: no queda nadie que le diga al navegador que suavice el regreso. Puesta en el estado normal, la instrucción está siempre, así que vale para la ida y para la vuelta.

La excepción es cuando se busca justamente eso: entrada suave y salida instantánea. Pero entonces es una decisión, no un descuido.

Los otros disparadores

:focus en un campo de formulario

El mismo mecanismo, sin ratón. Aquí el foco marca el campo activo con un borde y un halo, y sustituye el recuadro del navegador por otra señal igual de visible: quitar outline sin poner nada en su lugar deja la página inservible para quien navega con el teclado.

transition en :focus — pincha en el campo o llega con el tabulador

<input type="text" class="campo" placeholder="Escribe aquí">

:checked, el estado que se queda

Una casilla marcada sigue marcada cuando el ratón se va. Eso convierte a :checked en un interruptor de verdad, y permite abrir y cerrar cosas sin JavaScript. La dificultad está en el selector: hay que alcanzar desde la casilla el elemento afectado, y para eso sirve ~, que designa a un hermano posterior.

marca y desmarca la casilla

<input type="checkbox" id="sw">
<label for="sw">Encender</label>
<div class="row">
  <div class="caja luz">luz</div>
</div>

La casilla está antes que la fila en el HTML, y eso no es casual: CSS puede bajar y puede ir hacia la derecha, pero no puede subir ni retroceder. El disparador tiene que venir antes que lo disparado.

Qué se puede transicionar y qué no

La regla es una sola: solo se transiciona lo que tiene valores intermedios. Entre el rojo y el azul hay mil colores; entre 0 y 20 px hay mil medidas. Pero entre block y none no hay nada en medio.

Sí transicionanNo transicionan
colores (color, background-color, border-color) display
medidas (width, height, padding, margin, font-size) position
opacity flex-direction
transform en todas sus formas font-family
box-shadow, border-radius cualquier palabra sin punto medio

El caso que siempre aparece: hacer aparecer algo con display: none → display: block no se puede suavizar. La solución es no usar display, sino opacity para el desvanecido y visibility para que el elemento no quede estorbando cuando está invisible.

aparecer y desaparecer con opacity — pasa el ratón por la fila

<div class="row">
  <div class="caja">pasa el ratón</div>
  <div class="caja marcada escondida">aparezco</div>
</div>

visibility tampoco tiene valores intermedios, pero puesta en la lista espera: al ocultar, aguanta los 500 ms que tarda el desvanecido antes de retirar el elemento, en lugar de suprimirlo de inmediato. Es el procedimiento habitual.

Nótese que el hueco de la caja oculta sigue ahí: opacity: 0 y visibility: hidden no sacan al elemento del flujo. Si lo que se quiere es que no ocupe sitio, no hay transición posible y hay que usar display: none.

Resumen

  1. Una transición necesita un cambio de estado que la dispare. Sola no hace nada.
  2. Se escribe en el estado normal, no en el :hover.
  3. Lo único obligatorio es la duración.
  4. 150 - 400 ms para casi todo, y ease-out para lo que aparece.
  5. Solo se transiciona lo que tiene valores intermedios.
  6. Las dos propiedades más baratas para el navegador son transform y opacity. Transicionar width o margin obliga a recalcular la maqueta en cada fotograma, y en una página grande se nota.