SPA y MPA con View Transitions API
En este artículo revisaremos cómo usar View Transitions en una SPA y MPA. Y también cómo el framework Astro ya está brindando un soporte para MPA, mientras todavía no lo tenemos nativo desde el navegador.
La View Transitions API funciona de manera nativa para aplicaciones de una sola página (SPA). Y por otro lado, para el caso de aplicaciones multi página (MPA) sabemos que el equipo de Chrome ya está trabajando en ello.
Recordemos que la View Transitions API puede aplicarse a diferentes casos de uso donde se necesite realizar una transición animada cuando actualizamos el DOM.
Algunos casos de uso pueden ser: remover o agregar un elemento, cambiar una clase de CSS, navegación entre páginas, etc.
Si recién estás iniciando con esta API, antes de continuar te recomiendo revisar primero el siguiente artículo:
📝 Primeros pasos usando View Transitions API
View Transitions en SPA con React
Actualmente esta API está soportada para SPA en Chrome 111+. Su uso es mediante el método startViewTransition.
A este método startViewTransition solo le tenemos que pasar un callback donde especificamos la función que realiza la actualización del DOM que tendrá la transición.
El primer caso que veremos es un simple contador en React. El resultado visual es obtener la animación por defecto cross-fade.
Del código posiblemente tengas dudas del método flushSync. Básicamente permite forzar que la actualización se aplique de forma síncrona. Esto se realiza porque React maneja los cambios de estado de forma asíncrona.
El uso de flushSync es necesario para no generar conflictos que interrumpa el proceso de transición.
Por otro lado, también consideramos validar que el navegador utilizado soporte la API con la validación:
if (!document.startViewTransition) return
import { useState } from 'react'
import { flushSync } from 'react-dom'
export const Counter = () => {
const [counter, setCounter] = useState(0)
const add = () => setCounter(counter + 1)
const addWithTransition = () => {
if (!document.startViewTransition) return add()
document.startViewTransition(() => {
flushSync(() => add())
})
}
return (
<div>
<h1>Counter</h1>
<button onClick={add}>{counter}</button>
<button onClick={addWithTransition}>{counter}</button>
</div>
)
}
Realmente es bastante claro el uso de la API en un contador pero…
¿Cómo sería la navegación entre páginas?
La navegación en una SPA es un requerimiento usual e importante que puede llegar a ser complejo de mantener. Por eso se suele usar dependencias externas. Como por ejemplo para React podemos usar React Router o Wouter.
Estas dependencias se encargan de actualizar el DOM y nos brindan una abstracción que nos facilita diferentes tareas como el manejo de rutas, redirecciones, uso de parámetros, etc.
En el siguiente ejemplo tenemos React Router con View Transitions API.
Para entender mejor el uso de la API tenemos un custom hook que implementa los métodos goTo y goToWithTransition. Una simple abstracción sobre el método navigate de React Router para usar una navegación y transición a demanda.
import { Link, RouterProvider, createBrowserRouter, useNavigate } from 'react-router-dom'
import './App.css'
import { flushSync } from 'react-dom'
const ROUTES = {
home: '/',
about: '/about'
}
const useGoTo = () => {
const navigate = useNavigate()
const goTo = (route) => navigate(route)
const goToWithTransition = (route) => {
if (!document.startViewTransition) return goTo(route)
document.startViewTransition(() => {
flushSync(() => goTo(route))
})
}
return {
goTo,
goToWithTransition
}
}
const Home = () => {
const { goToWithTransition } = useGoTo()
return (
<div>
<h1>Home</h1>
<Link to={ROUTES.about}>Go to About</Link>
<button onClick={() => goToWithTransition(ROUTES.about)}>Go to About</button>
</div>
)
}
const About = () => {
const { goToWithTransition } = useGoTo()
return (
<div>
<h1>About</h1>
<Link to={ROUTES.home}>Go to home</Link>
<button onClick={() => goToWithTransition(ROUTES.home)}>Go to Home</button>
</div>
)
}
const router = createBrowserRouter([
{
path: ROUTES.home,
element: <Home />
},
{
path: ROUTES.about,
element: <About />
}
])
function App() {
return <RouterProvider router={router} />
}
export default App
Con los dos ejemplos ya vistos se puede empezar a utilizar la API fácilmente. Si deseas profundizar ten en cuenta que puedes utilizar el poder de CSS para personalizar las transiciones.
Puedes revisar el siguiente artículo del blog de Chrome:
📝 Transiciones fluidas y simples con la API de View Transitions
View Transitions en MPA
Actualmente no tenemos un soporte oficial. De momento sabemos que Chrome está trabajando en ello junto a otras características importantes.
En este caso Chrome nos dice que en lugar de usar startViewTransition, se utilizará el evento de navegación para indicar el momento de la transición.
Además de una metaetiqueta que habilitará el uso de la API.
<meta name="view-transition" content="same-origin">
Por ahora tenemos dos demos que puedes probar:
En el siguiente artículo de Chrome se menciona sobre esta y otras características que se esperan desarrollar.
📝 Las transiciones de la vista de SPA llegan a Chrome 111
View Transitions en MPA con JavaScript puro
Actualmente frameworks como Astro están brindando un soporte para usar View Transitions API en MPA. Lo logran haciendo que una MPA funcione como una SPA. Podemos llamarlo un falso MPA.
En el siguiente código podemos ver una referencia a la implementación. Donde tenemos lo mínimo de validaciones que nos permita probarlo con Javascript puro en una navegación entre 2 páginas.
Menciono lo mínimo de validaciones porque nos enfocaremos en actualizar solamente un contenido simple del body.
Aquí dentro del script tenemos primero un listener que captura los eventos de navegación. La idea es lograr interceptar dicho evento para que en lugar de navegar, optemos por obtener el contenido de la página destino.
Consideremos que toda navegación es un fetch de tipo GET que trae el contenido HTML, CSS y JavaScript según necesidad.
Entonces, del contenido obtenido sólo recuperamos el body y lo utilizamos para reemplazarlo con el body actual de la página.
Para la ejecución del ejemplo en mi caso utilizo la extensión de VSC llamada Live Server.
Código de index.html
< !doctype html >
<html>
<head>
<title>Hola Mundo con View Transitions API</title>
</head>
<body>
<header>
<nav>
<ul>
<li><a href="./">Inicio</a></li>
<li><a href="./about.html">Nosotros</a></li>
</ul>
</nav>
</header>
<main>
<h1>Inicio</h1>
</main>
<script>
navigation.addEventListener('navigate', (event) => {
const url = new URL(event.destination.url)
// verificar soporte de navegador
if (!document.startViewTransition) return
event.intercept({
async handler() {
// obtener el html de la page de destino
const response = await fetch(url.pathname)
const text = await response.text()
// del html solo necesitamos el contenido del body
const [, data] = text.match(/<body>([\s\S]*)<\/body>/i)
document.startViewTransition(() => {
document.body.innerHTML = data
})
}
})
})
</script>
</body>
</html>
Código de about
< !doctype html >
<html>
<head>
<title>Hola Mundo con View Transitions API</title>
</head>
<body>
<header>
<nav>
<ul>
<li><a href="./">Inicio</a></li>
<li><a href="./nosotros">Nosotros</a></li>
</ul>
</nav>
</header>
<main>
<h1>Nosotros</h1>
</main>
</body>
</html>
Realmente es interesante ver la simplificación de la lógica que se está utilizando para usar la API en una MPA.
Podemos pensar en algunas validaciones más en este flujo como:
- Validar que la navegación sea en el mismo origen.
- Validar la actualización del contenido del head de la página destino.
- Validar la actualización de scripts de la página destino.
Todas esas validaciones y posiblemente dolores de cabeza ya no nos preocupamos si usamos un framework como Astro.
Ahora que entendemos la lógica detrás podemos continuar y ver realmente qué beneficios nos brinda Astro respecto al uso de la View Transitions API.
View Transitions en MPA con Astro
Al utilizar Astro podemos usar la View Transitions API en una MPA de manera sencilla y con un buen soporte.
Inicialmente como configuración inicial podemos agregar el componente <ViewTransitions />
y por defecto ya tendremos el efecto cross-fade. Si lo aplicas en un layout ya lo tendrás disponible para toda navegación.
---
import { ViewTransitions } from 'astro:transitions';
---
<html lang="en">
<head>
<title>View Transitions</title>
<ViewTransitions />
</head>
<body>
<h1>Astro y View Transitions API</h1>
</body>
</html>
A partir de aquí Astro brinda directivas de transición que puedes utilizar para identificar elementos durante la transición o persistir elementos.
También tiene un ciclo de vida de eventos a considerar para evitar conflictos con el flujo normal de Astro.
También puedes revisar la sección donde explican la implementación del falso MPA en Astro.
📝 Proceso de navegación del lado del cliente
¿Un falso MPA?
Cuando entiendes un poco más la View Transitions API para MPA. Puedes tener dudas respecto a si es mejor usar una SPA en lugar de un falso MPA. O esperar que salga el soporte de la MPA oficial.
En realidad tenemos que considerar que el uso de la View Transitions API es un valor agregado a nuestros productos. Tanto SPA como MPA se aplican a necesidades diferentes.
El uso del llamado falso MPA es una implementación super válida por el contexto actual. Por ejemplo, varios sitios webs que se orientan a SEO pueden tener los beneficios de esta API si usan Astro.
En una MPA puedes tener dos maneras claras de manejar el renderizado. El renderizado en servidor o en tiempo de compilación (SSR y SSG).
En esos casos no es necesario que manejes una dependencia externa para las rutas o un estado en cliente. No puedes usar el método startViewTransition al navegar. Esa es una razón clara de porqué la implementación de un falso MPA es válida al aprovechar la API de navegación junto a la extracción del HTML necesario en cada petición de página.
¡Gracias por leer!
Realmente la View Transitions API es una característica muy interesante, fácil de utilizar y una mejora en experiencia.
Ya tenemos una idea base de cómo podemos empezar a aprovechar esta API para una SPA y MPA. Puedes utilizar la librería o framework que te interese, en mi caso React y Astro.