wandres.dev
TRANSITIONS · La animación de estado

transition-property, sus trampas y por qué all es mala idea

Cómo se resuelven las abreviaturas en la lista, qué pasa con las propiedades lógicas, y los cuatro problemas concretos de transition: all.

⏱ 17 min

transition-property parece la más simple de las cuatro: una lista de nombres. Tiene, sin embargo, tres comportamientos que no son evidentes y que producen transiciones que no arrancan o que arrancan de más: cómo se expanden las abreviaturas, cómo conviven las propiedades físicas con las lógicas, y qué significa exactamente all. La cuarta parte de la lección es el argumento completo contra all, que no es una cuestión de estilo sino de cuatro problemas concretos y medibles.

🎯 Al terminar esta lección sabrás
  • Predecir qué transiciones crea una abreviatura listada en transition-property.
  • Resolver el conflicto entre nombrar una propiedad física o su equivalente lógica.
  • Enumerar los cuatro problemas de transition: all con su mecanismo.
  • Escribir una lista de transiciones mantenible sin recurrir a all.

Las abreviaturas se expanden

Si listas una abreviatura en transition-property, el motor la expande a sus propiedades constituyentes y crea una transición para cada una. transition-property: background no crea una transición: crea una para background-color, otra para background-position, otra para background-size y así con todas las que componen la abreviatura.

Esto tiene dos consecuencias.

La buena es que funciona como esperas: escribir padding transiciona los cuatro lados sin tener que nombrarlos.

La mala es que la abreviatura arrastra propiedades que no querías. transition-property: background incluye background-image, que es discreta si son URLs; y transition-property: font incluye font-family, que es discreta siempre. En ambos casos no rompe nada, pero crea transiciones inútiles que el motor tiene que evaluar y que emiten eventos.

La consecuencia práctica más importante afecta a los eventos: como se emite un transitionend por propiedad, listar background en lugar de background-color multiplica por seis los eventos que recibe tu manejador. Si tu código cuenta eventos o reacciona al primero, el comportamiento cambia sin que nada visual lo indique.

Nombra siempre la propiedad final, no la abreviatura, salvo cuando de verdad quieres las cuatro caras de un padding.

Físicas y lógicas

CSS tiene dos vocabularios para lo mismo: margin-left y margin-inline-start, width y inline-size, top y inset-block-start. En un documento en horizontal de izquierda a derecha, cada par apunta a la misma propiedad física.

Para transition-property, la regla es que puedes nombrar cualquiera de las dos y funciona, porque el motor sabe a qué propiedad física se resuelve la lógica en el modo de escritura actual. Lo que no puedes hacer es asumir que nombrar una cubre a la otra en todos los modos de escritura: en un documento vertical, inline-size se resuelve a height, y si tu lista dice width no cubrirá el cambio.

La regla operativa es de coherencia: si escribes el resto del CSS con propiedades lógicas, escribe también la lista de transiciones con propiedades lógicas. Mezclar vocabularios es donde aparecen las transiciones que funcionan en un idioma y no en otro, que es un bug caro de encontrar porque nadie prueba el sitio en árabe.

/* Coherente y correcto en cualquier modo de escritura. */
.panel {
  inset-inline-start: 0;
  inline-size: 20rem;
  transition:
    inset-inline-start 240ms ease-out,
    inline-size 240ms ease-out;
}

all: qué es y por qué evitarlo

Qué significa exactamente all

transition-property: all significa “todas las propiedades que puedan transicionar”, que es un conjunto grande: más de doscientas propiedades en un motor moderno, incluyendo las custom properties registradas.

Y aquí está el detalle que sorprende: all no incluye las custom properties sin registrar, porque son discretas. Sí incluye las registradas con @property cuya sintaxis sea interpolable. Es decir, el conjunto de all cambia según lo que hayas registrado, lo cual es una fuente de sorpresas por sí sola.

Los cuatro problemas de all

Problema uno: anima propiedades que no pretendías, con coste desproporcionado. Es el que más duele. Un componente con transition: all 200ms y un estado que cambia height acaba disparando layout sesenta veces por segundo. Quien añadió el height no vio la transición; quien escribió la transición no imaginó el height. El coste está explicado en el nivel del pipeline y la diferencia entre animar una propiedad de composición y una de geometría es de dos órdenes de magnitud.

Problema dos: se dispara con cambios que no son interacciones. Un cambio de tema que altera veinte propiedades a la vez, un cambio de tamaño de la ventana que recalcula un clamp(), la carga de una fuente que cambia métricas. Con all, todos esos se convierten en transiciones. El síntoma típico es que al cambiar de tema claro a oscuro la página entera “se derrite” durante 200 milisegundos, que es un efecto que nadie pidió y que en listas largas cuesta muchísimo.

Problema tres: rompe patrones que dependen de que algo cambie de golpe. El más frecuente es @starting-style y las propiedades discretas del nivel 6: si all está activo, la transición se aplica a propiedades donde querías un salto inmediato. Otro caso: un elemento que se reposiciona al cambiar de contenedor. Con all, en lugar de aparecer en su sitio, viaja desde el anterior.

Problema cuatro: hace el CSS imposible de leer. Con all, para saber qué se anima en un componente hay que enumerar mentalmente todas las propiedades que cualquier estado pueda cambiar. Con una lista explícita, la respuesta está escrita. Esto no es una preocupación estética: es la diferencia entre poder revisar un cambio y no poder.

Si de verdad necesitas all, la respuesta correcta no es all sino su complemento

Hay un caso legítimo donde all parece la única opción: un componente que puede recibir estilos arbitrarios desde fuera y quieres que cualquier cambio se suavice. Ocurre en librerías de componentes, en editores visuales y en temas configurables. Cuando eso pasa, la respuesta no es aceptar all con sus cuatro problemas, es usar una herramienta que casi nadie asocia con transiciones: la lista explícita generada desde el sistema de diseño, más una regla de escape para lo que no debe animarse nunca. En la práctica se implementa así: mantienes una custom property con la lista de propiedades animables del sistema, la usas en todos los componentes, y ganas dos cosas que all no puede dar. La primera es que la lista es un artefacto revisable: cuando alguien añade una propiedad cara, aparece en un diff. La segunda es que puedes tener listas distintas por contexto —una para componentes de interacción, otra más corta para elementos que se repiten mucho en listas— y esa distinción es imposible de expresar con all. Y hay un tercer beneficio que solo se aprecia cuando llega el problema: si un día necesitas desactivar todas las transiciones del sistema durante una operación —un cambio de tema, una recolocación masiva—, con all tienes que pelear con la cascada componente a componente, mientras que con una variable compartida basta con redefinirla a none en un contenedor y todo el subárbol deja de animarse en una línea, sin !important y sin efectos colaterales.

:root {
  /* La lista del sistema: revisable, compartida y desactivable. */
  --props-animables:
    color, background-color, border-color, opacity,
    translate, rotate, scale, box-shadow;
  --dur: 200ms;
  --ease: cubic-bezier(0.2, 0, 0, 1);
}

.componente {
  transition-property: var(--props-animables);
  transition-duration: var(--dur);
  transition-timing-function: var(--ease);
}

/* Desactivar todo el subarbol durante una operacion masiva. */
[data-sin-transiciones] {
  --props-animables: none;
}

Ese último bloque es el patrón para cambiar de tema sin que la página se derrita: pones el atributo, cambias el tema, esperas un evento de cambio de estilo y lo quitas.

async function cambiarTema(nuevo) {
  const raiz = document.documentElement;
  raiz.dataset.sinTransiciones = '';
  raiz.dataset.tema = nuevo;

  // Fuerza un evento de cambio de estilo para que el nuevo tema
  // se compute sin transiciones activas.
  getComputedStyle(raiz).transitionProperty;

  delete raiz.dataset.sinTransiciones;
}

Esa lectura de getComputedStyle no es un truco decorativo: es el mismo mecanismo que estudia la lección siguiente, usado a propósito y en la dirección contraria.

Escribir listas mantenibles

Tres convenciones cubren la práctica totalidad de los casos sin recurrir nunca a all.

Una entrada completa por propiedad en la abreviatura. Elimina el emparejamiento posicional y hace que cada transición se lea entera.

Nombres finales, no abreviaturas. background-color, no background. Menos eventos, menos transiciones inútiles, más claridad.

La lista como variable cuando se repite. Si tres componentes comparten el mismo conjunto, extráelo. Deja de haber divergencia y aparece un punto único donde revisar el coste.

⚔️ Elimina un all real
  1. Busca en un proyecto todas las apariciones de transition: all o de una abreviatura sin nombre de propiedad. Cuéntalas.
  2. Para cada una, enumera qué propiedades puede cambiar ese componente en algún estado. Anota cuáles disparan layout.
  3. Sustituye por listas explícitas y mide el peor fotograma antes y después en el componente que más propiedades tenía.
  4. Implementa el patrón de desactivación por variable y comprueba que un cambio de tema deja de animarse.