View Transitions

EmberKit integra la View Transitions API para navegación SPA fluida. Las transiciones esperan hasta que el HTML de la ruta se escribe en la raíz de la app antes de que el navegador capture snapshots.

Inicio rápido

Habilita transiciones al arrancar el router de cliente:

// src/index.tsx
import { render } from '@emberkit/core';
import App from './routes/_layout.tsx';
import { routes } from 'virtual:emberkit-routes';

render(App, document.getElementById('app')!, {
  routes,
  viewTransitions: true,
});

Los enlaces internos se interceptan en fase capture para que la navegación se ejecute una sola vez dentro de startViewTransition.

import { navigate, navigateWithViewTransition } from '@emberkit/core';

// Per navigation
await navigate('/docs/api', { viewTransition: true });

// Dedicated helper
await navigateWithViewTransition('/docs/api');

CSS

Estiliza el cross-fade por defecto en la raíz:

::view-transition-old(root) {
  animation: fade-out 0.3s ease-in-out forwards;
}
::view-transition-new(root) {
  animation: fade-in 0.3s ease-in-out forwards;
}

Este sitio de docs usa reglas similares en src/styles/globals.css.

Referencia API

ExportDescripción
supportsViewTransitions()Si existe document.startViewTransition
withViewTransition(callback)Envuelve trabajo async en una transición cuando está soportado
waitForAppUpdate(href, options?)Navegación SPA que resuelve tras mutar #app
initViewTransitions(options?)Interceptor de clicks en enlaces (llamado automáticamente por render)
navigateWithViewTransition(href, options?)Navegar con transición + espera de actualización DOM

Opción de render():

viewTransitions?: boolean | { rootId?: string };

Usa rootId si tu elemento de montaje no es id="app".

Cómo funciona el timing

  1. El usuario hace click en un enlace interno (o llamas a navigate con viewTransition: true).
  2. EmberKit inicia una view transition.
  3. history.pushState (o replaceState) ejecuta el router parcheado.
  4. renderCurrentRoute() actualiza el HTML de la raíz de la app.
  5. Un MutationObserver en la raíz de la app resuelve cuando cambian nodos hijos.
  6. El navegador anima del snapshot antiguo al nuevo.

Esto evita flashes de contenido vacío entre rutas.

Opt-out por enlace

Añade data-no-transition en un anchor, o usa teclas modificadoras (Ctrl/Cmd click). Enlaces externos, target="_blank" y enlaces solo hash en la misma página se omiten automáticamente.

Compatibilidad de navegadores

NavegadorSoporte
Chrome / Edge 111+API completa
Safari / FirefoxFallback a navegación instantánea (sin errores)

Los usuarios con prefers-reduced-motion deben obtener movimiento reducido del navegador.

Patrón del sitio de docs

Esta app usa un wrapper fino para que cada llamada a useNavigate() habilite transiciones por defecto:

// apps/docs/src/hooks/useNavigate.ts
import { navigate as coreNavigate } from '@emberkit/core';

export function useNavigate() {
  return (path: string, options = {}) =>
    coreNavigate(path, { ...options, viewTransition: options.skipTransition ? false : true });
}

Relacionado