wandres.dev
VIEW TRANSITIONS I · Mismo documento

El problema que resuelve startViewTransition

Por qué animar entre dos estados del DOM era imposible antes, qué hace exactamente la llamada, y en qué se diferencia de todo lo anterior.

⏱ 17 min

Hay una clase entera de animaciones que la web no podía hacer, y no por falta de API sino por una imposibilidad estructural: no puedes interpolar entre dos estados de una interfaz cuando el primero deja de existir en el instante en que aparece el segundo. Borrar un elemento de una lista, cambiar el contenido de un panel, pasar de una rejilla a un detalle: en todos esos casos el estado antiguo se destruye y no hay nada entre lo que animar. La API de View Transitions no añade una función de animación; añade la capacidad de tener los dos estados a la vez.

🎯 Al terminar esta lección sabrás
  • Explicar por qué el patrón FLIP existía y qué problema no resolvía.
  • Escribir la llamada mínima a document.startViewTransition y saber qué hace por defecto.
  • Distinguir qué parte del trabajo hace el navegador y qué parte sigue siendo tuya.
  • Situar el estado de soporte y la clase de fallo que produce su ausencia.

Lo que había antes y por qué no bastaba

El estado del arte hasta 2023 era el patrón FLIP, que es una idea excelente y una respuesta parcial. Consiste en medir las posiciones antes del cambio, aplicar el cambio, medir después, calcular la transformación que llevaría de la primera posición a la segunda, aplicarla invertida, y animarla hasta la identidad. Cuatro pasos y dos lecturas forzadas de layout, con el resultado de que un elemento parece moverse de donde estaba a donde está.

FLIP resuelve bien el caso en que el mismo elemento cambia de posición o tamaño: reordenar una lista, expandir una tarjeta. Y no resuelve en absoluto los dos casos que más se piden. El primero es la sustitución de contenido: si el panel pasa de mostrar A a mostrar B, no hay ningún elemento común que medir, porque A ya no está en el DOM. El segundo es el cambio de vista completo: al pasar de una rejilla de fotos a la vista de detalle, lo que quieres es que la miniatura crezca hasta convertirse en la foto grande, y son dos elementos distintos, en dos estructuras distintas, con dos ciclos de vida distintos.

Lo que faltaba no era una técnica de animación. Era la posibilidad de congelar el estado antiguo mientras el nuevo se construye, y disponer de los dos simultáneamente como material animable. Eso es exactamente lo que hace document.startViewTransition.

La llamada mínima

function borrarTarea(id) {
  document.startViewTransition(() => {
    document.getElementById(id).remove();
  });
}

Eso es todo lo que hace falta para que el navegador haga un fundido cruzado entre el antes y el después. Sin una línea de CSS, sin medir nada, sin FLIP.

El argumento es una función que muta el DOM. No es un callback de “cuando termine”, ni un manejador de eventos: es el trozo de código que realiza el cambio, y el navegador lo llama en el momento exacto en que ya ha capturado el estado antiguo y está listo para capturar el nuevo. Ese detalle de diseño es lo que hace que la API sea utilizable: no tienes que coordinar nada, solo entregarle tu mutación.

La función puede ser asíncrona, y el navegador esperará a que su promesa se resuelva antes de capturar el estado nuevo:

async function irADetalle(url) {
  const html = await fetch(url).then((r) => r.text());

  document.startViewTransition(async () => {
    document.querySelector('main').innerHTML = html;
    await document.fonts.ready;
  });
}

Aquí hay una trampa que conviene señalar ya: durante la ejecución del callback la página está congelada visualmente, mostrando la captura del estado antiguo. Si dentro del callback esperas una petición de red lenta, el usuario ve una imagen fija que no responde. La forma correcta es traer los datos antes de abrir la transición, como en el ejemplo, y dejar dentro del callback solo la mutación. El navegador tiene un tiempo máximo de espera y aborta la transición si el callback tarda demasiado, precisamente para que un error tuyo no congele la interfaz para siempre, pero no conviene depender de ese salvavidas.

Qué hace el navegador y qué te toca a ti

La división del trabajo es la parte que hay que interiorizar, porque determina qué se puede personalizar.

El navegador se encarga de: capturar el estado visual antiguo, bloquear el renderizado mientras muta el DOM, capturar el estado nuevo, construir un árbol de pseudo-elementos con las dos capturas, ejecutar unas animaciones por defecto sobre ese árbol, y desmontarlo al terminar.

A ti te toca: proporcionar la mutación, decidir qué elementos merecen captura propia poniéndoles nombre, y escribir el CSS que sustituya a las animaciones por defecto si quieres algo distinto del fundido.

Las animaciones por defecto son un fundido cruzado de un cuarto de segundo entre las dos capturas de la página entera. Es deliberadamente aburrido, y es la elección correcta: da un resultado aceptable sin configuración y deja claro que lo interesante empieza cuando pones nombres.

Esto no es una API de animación, es una API de captura

Merece la pena resistirse a la lectura fácil, porque orienta mal todo lo que viene después. startViewTransition no anima nada: lo que hace es fabricarte, en el momento justo, un conjunto de pseudo-elementos que contienen dos imágenes del mismo trozo de interfaz, una de antes y otra de ahora, colocadas en el sitio donde estaban sus originales. La animación posterior es CSS normal y corriente sobre esos pseudo-elementos, con las mismas propiedades, el mismo animation-timing-function y las mismas reglas de composición que llevas usando desde el nivel 8. De ahí salen tres consecuencias que ahorran mucho tiempo. Primera: todo lo que sabes de animación CSS se aplica tal cual; no hay un sistema nuevo que aprender, hay un conjunto de pseudo-elementos nuevo que seleccionar. Segunda: si una transición no se ve como quieres, el problema casi nunca está en la llamada de JavaScript, está en el CSS que aplica a esos pseudo-elementos, y por tanto se depura con el inspector de estilos, no con puntos de ruptura. Tercera, y la más útil: puedes usar la API sin animar nada. Envolver una mutación en startViewTransition y sobrescribir las animaciones por defecto con animation: none produce un cambio instantáneo pero atómico: el navegador garantiza que no se ve ningún estado intermedio del DOM a medio actualizar. Ese uso, sin una sola animación, ya resuelve por sí solo la clase de parpadeos que aparecen cuando una actualización toca varios sitios del documento a la vez.

Soporte y modo de fallo

Las transiciones de vista de mismo documento están en los tres motores: Chromium desde la 111, Safari desde la 18 y Firefox desde la 144. Con eso pasaron a ser Baseline en 2025, lo que las convierte en la única parte de este bloque que puedes usar sin condicionales.

El modo de fallo es de los mejores que existen. En un navegador antiguo, document.startViewTransition sencillamente no existe, y la comprobación es de una línea:

function actualizar(mutacion) {
  if (!document.startViewTransition) {
    mutacion();
    return;
  }
  document.startViewTransition(mutacion);
}

Sin la API, la mutación ocurre igual y de forma instantánea. El usuario ve el cambio sin animación, que es exactamente lo que veía antes de que la API existiera. Fallo cosmético puro: no hay ningún caso en que la ausencia de transiciones de vista deje contenido inaccesible, siempre que respetes la regla de que la mutación es tuya y el navegador solo la envuelve.

Esa función de tres líneas es la que conviene tener en el proyecto desde el principio, porque además centraliza el sitio donde luego añadirás tipos de transición y la comprobación de prefers-reduced-motion. Escribir document.startViewTransition disperso por veinte ficheros es la forma de garantizar que ninguna de esas mejoras se aplique de forma consistente.