7 de julio de 20265 min de lectura

View transitions en Astro: cómo activarlas y por qué las mías estaban apagadas

Compartir

Serie «Efectos con Tailwind» · 4 de 4

  1. Crear un borde animado con Tailwind CSS
  2. Glassmorphism sin arruinar la legibilidad
  3. Scroll-driven animations
  4. View transitions en Astro ← estás aquí

Una view transition es lo que hace que, al abrir un artículo desde un listado, la imagen de la tarjeta no desaparezca y vuelva a cargarse, sino que se desplace hasta su nueva posición y tamaño. Es un detalle pequeño, pero hace que la navegación se sienta mucho más fluida.

Escribí este artículo porque, revisando mi propio blog, encontré siete elementos con transiciones declaradas desde hacía meses y ninguna funcionaba. Cuento por qué, porque es un error fácil de cometer y no da ningún aviso.

Cómo funciona: pares de nombres

El mecanismo es sencillo: le pones el mismo nombre a un elemento en la página de origen y a su equivalente en la de destino:

<!-- En el listado -->
<img src="/portada.webp" style="view-transition-name: portada-mi-post" />

<!-- En el artículo -->
<img src="/portada.webp" style="view-transition-name: portada-mi-post" />

El navegador ve el mismo nombre en las dos páginas, entiende que es el mismo objeto y anima la diferencia de posición, tamaño y opacidad. No tienes que escribir la animación.

En Astro esto se declara con la directiva transition:name, que es más cómoda:

<img src={image.url} alt={image.alt} transition:name={`image-${image.url}`} />

Mi error: los nombres sin activar las transiciones

transition:name solo pone la etiqueta; no activa nada.

Para que haya transición, el navegador también tiene que saber que las navegaciones se deben animar, y eso es una pieza aparte que yo no tenía en ningún sitio del proyecto. El resultado eran siete transition:name generando su CSS correctamente y ninguna transición, sin un solo error en la consola.

Si sospechas que te pasa lo mismo, puedes comprobarlo rápido desde la raíz de tu proyecto:

grep -rn "ClientRouter\|@view-transition" src/

Si eso no devuelve nada y sí usas transition:name, estás en la misma situación en la que estaba yo.

Hay dos formas de activarlas.

Opción 1: CSS nativo, sin JavaScript

La forma más actual es una regla de CSS de dos líneas en tu hoja de estilos global:

@view-transition {
  navigation: auto;
}

Con eso el navegador anima por sí solo la navegación entre páginas. No hay router, ni JavaScript, ni peso añadido: la web sigue siendo una web normal con enlaces normales.

Por eso es la que prefiero, y la que uso en este blog. Su límite es que depende de que el navegador soporte transiciones entre documentos; donde no hay soporte, los enlaces funcionan igual, solo que sin animación. Puedes consultarlo en Can I use.

Opción 2: el <ClientRouter /> de Astro

La alternativa es el router de Astro, que intercepta los enlaces y hace la navegación con JavaScript:

---
import { ClientRouter } from "astro:transitions";
---

<html lang="es">
  <head>
    <ClientRouter />
  </head>
  <!-- ... -->
</html>

A cambio del JavaScript que añade, ofrece cosas que el CSS nativo no: funciona en más navegadores, permite controlar las animaciones con transition:animate y tiene transition:persist para mantener un elemento entre páginas, útil para un reproductor de audio o un menú abierto.

Para elegir: si solo quieres que las imágenes y los títulos pasen de una página a otra, usa el CSS nativo. Si necesitas mantener estado o controlar cada animación, usa ClientRouter.

El segundo fallo: nombres duplicados

Este es más difícil de ver.

Un view-transition-name tiene que ser único dentro de una misma página. Si dos elementos comparten nombre, el navegador no elige uno: cancela toda la transición.

Y a mí me estaba pasando. En la portada de mi blog, el post más reciente aparecía dos veces: una en la tarjeta grande de la cabecera y otra en el listado de abajo. Los dos elementos generaban el mismo nombre a partir de la URL de la imagen, así que el HTML tenía esto:

2 × view-transition-name: image-_2fimages_2fposts_2faccesibilidad_2ewebp

Dos ocurrencias del mismo nombre. Aunque hubiera tenido el motor encendido, esa página no habría animado nunca.

Para detectarlo hay que mirar el HTML generado, no el código fuente, porque el duplicado nace de que dos componentes distintos rendericen el mismo post:

npm run build
grep -oE 'view-transition-name: *[^;"]*' dist/blog/index.html | sort | uniq -d

Si uniq -d devuelve algo, esas son tus transiciones canceladas. Vale la pena tener el comando a mano.

Lo arreglé por los dos lados. Primero, no repetir el post: el componente del listado ya aceptaba una prop para excluir el más reciente, y bastó con usarla. Y segundo, dejar de derivar el nombre de la URL de la imagen y sacarlo del slug del post, que es único:

// utils/transitions.ts
export const postSlug = (url?: string) =>
  (url ?? "").replace(/\/+$/, "").split("/").pop() ?? "";

El primer arreglo quitó el duplicado de ese momento y el segundo evita que vuelva a aparecer. En general, si generas los nombres a partir de datos (un slug, una URL, un id), asegúrate de que ese dato no se pueda repetir en la misma página. Dos artículos pueden compartir portada, pero no slug.

Nombrar bien

Tres reglas que me habrían ahorrado todo esto:

  • Saca el nombre de algo estable y único. El slug del post es mejor que el título, y mucho mejor que la URL de la imagen, porque dos artículos pueden usar la misma portada.
  • En los listados, cada tarjeta necesita un nombre distinto. Solo va a animarse la que el usuario abra, pero como no sabes cuál será, todas tienen que tenerlo, y nunca repetido.
  • Anima pocas cosas. Una imagen y un título por transición bastan; cuantos más elementos se mueven, más fácil es que alguno se desalinee y el efecto se vea desordenado.

Accesibilidad

Como con cualquier movimiento, hay que respetar a quien pidió que la interfaz no se mueva:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

Esto desactiva la animación y deja el cambio de página instantáneo, que es lo que esa persona pidió. La navegación sigue funcionando; solo desaparece el movimiento.

Con esto termina la serie. Si llevas tiempo con transition:name puesto, vale la pena comprobar hoy mismo que las transiciones estén activadas: yo pasé meses sin ellas sin darme cuenta.

Compartir