Animaciones a medida y accesibilidad del movimiento
Bajar de las animaciones integradas a la View Transitions API del navegador: los pseudo-elementos que expone (view-transition, group, image-pair, old y new), cómo animarlos con keyframes de CSS apuntando a un transition:name, cómo construir un objeto de animación propio para transition:animate, y la responsabilidad ineludible de respetar prefers-reduced-motion para que el movimiento no dañe a quien lo padece.
Los pseudo-elementos que el navegador te presta
Cuando ocurre una view transition, el navegador hace algo ingenioso: toma una captura del estado viejo y del nuevo, y las organiza en un árbol de pseudo-elementos que existe solo durante la animación y que puedes estilar con CSS. En la raíz está ::view-transition, que cubre toda la página. Por cada view-transition-name —el que Astro asigna con transition:name— cuelga un ::view-transition-group(nombre), y dentro un ::view-transition-image-pair(nombre) que contiene las dos capturas: ::view-transition-old(nombre), la instantánea del elemento saliente, y ::view-transition-new(nombre), la del entrante. Animar la transición es, literalmente, animar esas dos capturas.
La palabra clave especial es root: ::view-transition-old(root) y ::view-transition-new(root) son las capturas de la página entera, la transición por defecto. Apuntando a ellas rediseñas el fundido global; apuntando a un nombre concreto, rediseñas el de ese elemento.
Animar con keyframes de CSS
El patrón es directo. Defines tus @keyframes y los aplicas al pseudo-elemento del nombre que quieras animar, en una hoja de estilos global —porque estos pseudo-elementos viven en el documento, no dentro del scope de un componente—. Supongamos que quieres que el contenido principal entre desde abajo con un desvanecido en lugar del fundido plano por defecto.
---// src/pages/index.astro---<main transition:name="principal"> <slot /></main><style is:global> @keyframes entrar-desde-abajo { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } @keyframes salir-hacia-arriba { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-30px); } } ::view-transition-new(principal) { animation: entrar-desde-abajo 0.35s ease-out; } ::view-transition-old(principal) { animation: salir-hacia-arriba 0.35s ease-in; }</style>Fíjate en la simetría: la captura vieja se va con una animación, la nueva llega con otra. Controlas cada mitad por separado, con la duración y la curva que decidas. Todo lo que sabes de @keyframes de CSS —retardos, rebotes, encadenados— se aplica aquí sin nada nuevo que aprender, porque estás animando elementos normales que el navegador ha fabricado para ti.
Un objeto de animación para transition:animate
Escribir CSS global funciona, pero Astro ofrece una vía más encapsulada y reutilizable: transition:animate acepta, además de las animaciones con nombre, un objeto que describe la animación por completo. Ese objeto tiene dos ramas —forwards, para el salto hacia delante, y backwards, para cuando el usuario pulsa atrás— y cada rama define cómo se anima la captura vieja (old) y la nueva (new): el nombre del keyframe, su duración, su curva y su modo de relleno.
// src/animaciones.tsimport type { TransitionDirectionalAnimations } from 'astro';const comun = { duration: '0.3s', easing: 'ease-out', fillMode: 'both' };export const zoom: TransitionDirectionalAnimations = { forwards: { old: { name: 'zoom-salir', ...comun }, new: { name: 'zoom-entrar', ...comun }, }, backwards: { old: { name: 'zoom-salir', ...comun }, new: { name: 'zoom-entrar', ...comun }, },};---import { zoom } from '../animaciones';---<section transition:animate={zoom}> <slot /></section>Los keyframes zoom-entrar y zoom-salir siguen definiéndose en CSS, pero ahora la animación es un valor de JavaScript que importas y aplicas donde quieras, con la ventaja de que distingue el sentido del viaje: puedes hacer que ir hacia delante y volver atrás se sientan como movimientos opuestos, igual que hace slide de fábrica.
La accesibilidad no es opcional: prefers-reduced-motion
Aquí cambia el tono, porque cambia lo que está en juego. Para una parte real de las personas, el movimiento en pantalla no es un adorno agradable sino un disparador de mareo, náusea o desorientación —es el trastorno vestibular, y es tan real como la fotosensibilidad—. Los sistemas operativos permiten a esas personas pedir que se reduzca el movimiento, y el navegador expone esa petición como la media query prefers-reduced-motion. Ignorarla no es un descuido estético: es construir algo que puede enfermar a quien lo usa.
La buena noticia es que las animaciones integradas de Astro ya respetan esta preferencia: cuando el usuario pide menos movimiento, fade y slide recortan su animación por ti. La responsabilidad es tuya en cuanto escribes CSS propio. Toda animación que definas a mano debe envolverse de modo que solo corra cuando el usuario no ha pedido reducirla.
<style is:global> @media (prefers-reduced-motion: no-preference) { ::view-transition-new(principal) { animation: entrar-desde-abajo 0.35s ease-out; } ::view-transition-old(principal) { animation: salir-hacia-arriba 0.35s ease-in; } }</style>Al condicionar la animación a no-preference, quien no ha expresado preferencia disfruta del movimiento y quien pidió reducirlo recibe una transición sobria —un cambio limpio, sin desplazamientos—. También puedes tomar el camino inverso: definir la animación siempre y anularla dentro de @media (prefers-reduced-motion: reduce). Cualquiera de las dos formas cumple; lo que no cumple es no hacer nada.