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.
Navegación programática
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
| Export | Descripció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
- El usuario hace click en un enlace interno (o llamas a
navigateconviewTransition: true). - EmberKit inicia una view transition.
history.pushState(oreplaceState) ejecuta el router parcheado.renderCurrentRoute()actualiza el HTML de la raíz de la app.- Un
MutationObserveren la raíz de la app resuelve cuando cambian nodos hijos. - 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
| Navegador | Soporte |
|---|---|
| Chrome / Edge 111+ | API completa |
| Safari / Firefox | Fallback 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 });
}