Las cuatro propiedades y la abreviatura
transition-property, duration, timing-function y delay como sistema, cómo se emparejan sus listas, y las trampas de la abreviatura.
Las transiciones se configuran con cuatro propiedades independientes que casi nadie escribe por separado, porque la abreviatura es cómoda. El precio de esa comodidad es que dos comportamientos importantes quedan escondidos: cómo se emparejan listas de longitudes distintas, y qué reinicia la abreviatura cuando la usas. Los dos producen bugs que se diagnostican mal, porque el CSS se lee bien y hace otra cosa.
- Enumerar las cuatro propiedades con su valor inicial y su significado exacto.
- Predecir el resultado de listas de longitudes distintas.
- Explicar qué reinicia la abreviatura y por qué eso rompe
transition-behavior. - Usar retrasos negativos y saber qué hacen realmente.
Las cuatro, con sus valores iniciales
| Propiedad | Valor inicial | Qué controla |
|---|---|---|
transition-property |
all |
Qué propiedades pueden transicionar |
transition-duration |
0s |
Cuánto dura la interpolación |
transition-timing-function |
ease |
Cómo se distribuye el progreso |
transition-delay |
0s |
Cuánto se espera antes de empezar |
El detalle que explica por qué no hay transiciones por todas partes es la combinación de los dos primeros valores iniciales: transition-property vale all por defecto, pero transition-duration vale 0s, y una duración de cero significa que no hay interpolación. Es decir, por defecto todo puede transicionar y nada transiciona. Basta con declarar una duración para que se active para todas las propiedades, que es justo el problema del que hablaremos en la lección sobre all.
transition-timing-function tiene un valor inicial que sorprende a quien viene de otras plataformas: ease, que equivale a cubic-bezier(0.25, 0.1, 0.25, 1). No es linear ni ease-out. Es una curva con entrada rápida y salida larga, razonable como valor por defecto general y raramente la mejor para un caso concreto.
transition-delay acepta valores negativos, y su efecto no es adelantar: es empezar la transición ya avanzada. Con transition-delay: -100ms sobre una transición de 300, la interpolación arranca en el punto que corresponde a los 100 milisegundos y dura 200 en total. Es útil para desincronizar elementos de un grupo sin cambiar cuándo terminan todos, que es lo contrario de lo que hace un retraso positivo.
El emparejamiento de listas
Cada una de las cuatro propiedades acepta una lista separada por comas. Lo que manda es transition-property: su longitud determina cuántas transiciones hay. Las demás listas se emparejan por posición, y si son más cortas, se repiten cíclicamente. Si son más largas, sobran valores y se ignoran.
.ejemplo {
transition-property: opacity, translate, scale, filter;
transition-duration: 200ms, 400ms;
transition-timing-function: linear, ease-out, ease-in;
transition-delay: 0ms;
}
El resultado del emparejamiento es este:
| Propiedad | Duración | Curva | Retraso |
|---|---|---|---|
opacity |
200ms | linear |
0ms |
translate |
400ms | ease-out |
0ms |
scale |
200ms | ease-in |
0ms |
filter |
400ms | linear |
0ms |
El ciclado es una decisión de diseño razonable —permite escribir una duración para todas— y es una fuente de errores silenciosos cuando alguien añade una propiedad a la primera lista y no ajusta las demás. La duración de la nueva propiedad sale del ciclo y puede ser cualquier cosa.
La defensa es sencilla y merece la pena adoptarla como convención: si las duraciones no son todas iguales, usa la abreviatura con una entrada por propiedad. Deja de haber emparejamiento posicional y cada transición se lee completa en una línea.
/* Sin ambiguedad: cada transicion completa en su propia entrada. */
.ejemplo {
transition:
opacity 200ms linear,
translate 400ms ease-out,
scale 200ms ease-in,
filter 400ms linear;
}
La abreviatura y lo que reinicia
transition es una abreviatura, y como toda abreviatura en CSS, reinicia a su valor inicial todas las propiedades que la componen y que no menciones explícitamente. Eso incluye una que no es de las cuatro clásicas y que se añadió después: transition-behavior.
La consecuencia es un orden obligatorio que rompe código constantemente:
/* Roto: la abreviatura reinicia transition-behavior a normal. */
.mal {
transition-behavior: allow-discrete;
transition: opacity 300ms, display 300ms;
}
/* Correcto: dentro de la abreviatura, por entrada. */
.bien {
transition: opacity 300ms, display 300ms allow-discrete;
}
/* Tambien correcto: la propiedad separada, despues. */
.tambien-bien {
transition: opacity 300ms, display 300ms;
transition-behavior: allow-discrete;
}
La forma recomendada es la segunda, porque allow-discrete casi nunca se quiere para todas las propiedades de la lista: se quiere para la propiedad discreta concreta. Ponerla como palabra clave dentro de la entrada correspondiente es más preciso y no depende del orden de las declaraciones.
Dentro de una entrada de la abreviatura, el orden de los componentes tiene una regla que hay que conocer: el primer valor de tiempo es la duración y el segundo es el retraso. Los demás componentes pueden ir en cualquier orden.
/* 300ms de duracion, 100ms de retraso. */
.a { transition: opacity 300ms 100ms ease-out; }
/* 100ms de duracion, 300ms de retraso. Cambia todo. */
.b { transition: opacity 100ms 300ms ease-out; }
Escribir transition: 200ms ease-out sin nombrar la propiedad parece un atajo inocente y es probablemente la línea de CSS que más regresiones de rendimiento ha causado en sistemas de diseño. Al omitir la propiedad, transition-property cae a su valor inicial, que es all, así que acabas de declarar que cualquier propiedad de ese elemento se interpolará durante 200 milisegundos. Hoy no pasa nada porque el componente solo cambia de color al pasar el ratón. El problema llega dentro de seis meses, cuando alguien añade un estado que cambia height, o grid-template-columns, o box-shadow, y de repente ese componente dispara layout o repintados carísimos sesenta veces por segundo, y quien lo añadió no tocó la transición porque ni siquiera la vio. Peor todavía en componentes que se repiten: una tabla con doscientas filas donde cada fila tiene transition: 200ms es una tabla donde cualquier cambio de estilo masivo —un cambio de tema, una recolocación— arranca doscientas transiciones simultáneas de propiedades que nadie pretendía animar. Y el diagnóstico es especialmente difícil porque el síntoma aparece al hacer un cambio que no tiene nada que ver, en un fichero distinto, y la traza señala una propiedad que en el CSS del componente no aparece por ningún sitio. La regla es corta y no admite excepción práctica: nombra siempre la propiedad. Si de verdad quieres animar varias, escríbelas todas; si son tantas que escribirlas es tedioso, eso es la señal de que el componente cambia demasiadas cosas a la vez y el problema no es la transición.
Retrasos negativos y grupos
Los retrasos positivos son la herramienta obvia para escalonar y tienen un coste: alargan el tiempo total. Los negativos hacen lo contrario y son útiles para un caso concreto: desincronizar el arranque sin retrasar el final.
Compara los dos enfoques sobre un grupo de cinco elementos con una transición de 300 milisegundos:
/* Positivo: los cinco tardan lo mismo pero el ultimo acaba en 540ms. */
.fila:nth-child(1) { transition-delay: 0ms; }
.fila:nth-child(2) { transition-delay: 60ms; }
.fila:nth-child(3) { transition-delay: 120ms; }
.fila:nth-child(4) { transition-delay: 180ms; }
.fila:nth-child(5) { transition-delay: 240ms; }
/* Negativo: todos empiezan a la vez y todos acaban en 300ms,
pero cada uno arranca desde un punto distinto de la curva. */
.fila:nth-child(1) { transition-delay: 0ms; }
.fila:nth-child(2) { transition-delay: -60ms; }
.fila:nth-child(3) { transition-delay: -120ms; }
.fila:nth-child(4) { transition-delay: -180ms; }
.fila:nth-child(5) { transition-delay: -240ms; }
El resultado visual del segundo no es un escalonado: es que los elementos aparecen ya parcialmente avanzados y convergen. Sirve para dar textura a un grupo sin que el grupo tarde más, y es una herramienta que se usa poco porque el efecto se entiende mal cuando se lee la propiedad.
Hay una limitación importante que conviene tener presente: un retraso negativo mayor que la duración hace que la transición ya haya terminado, es decir, el elemento salta directamente al valor final sin interpolar nada. Con transition: opacity 300ms -400ms, la opacidad cambia de golpe.
- Escribe una regla con cuatro propiedades en
transition-propertyy solo dos duraciones. Predice el emparejamiento antes de probarlo y compruébalo congetComputedStyle. - Añade una quinta propiedad y comprueba qué duración le tocó.
- Reescríbelo con la abreviatura completa por propiedad y verifica que el resultado computado es el que querías.
- Declara
transition-behavior: allow-discreteantes de una abreviatura y comprueba congetComputedStyleque se ha perdido.