El problema
Las transiciones de vistas entre documentos animan elementos entre dos páginas durante una navegación del mismo origen. El navegador captura una instantánea de la página anterior, navega y, luego, anima desde la instantánea hasta la página nueva. Si la página nueva no terminó de cargar los recursos críticos (hojas de estilo, secuencias de comandos de diseño o elementos clave del DOM), la transición se anima a un estado incompleto o sin diseño. Esto causa fallas visuales, como elementos que se transforman en posiciones incorrectas, contenido que se redistribuye en medio de la animación o fuentes de resguardo que parpadean en fuentes web después de que se completa la transición.
La solución
Usa blocking="render" en los elementos <link> y <script> críticos en el <head> de la página nueva, y usa <link rel="expect"> para bloquear la renderización hasta que se hayan analizado elementos específicos del DOM. Esto garantiza que el navegador no comience la animación de transición de vistas hasta que el estado visual de la página nueva sea estable. El navegador sigue analizando el HTML en segundo plano; solo se aplaza la pintura.
Estrategia de implementación
- OBLIGATORIO: Habilita las transiciones de vistas entre documentos con la regla-at de CSS
@view-transitionen ambas páginas. - OBLIGATORIO: Asegúrate de que las hojas de estilo fundamentales estén en el
<head>. Las hojas de estilo en<head>bloquean la renderización de forma predeterminada. Las hojas de estilo insertadas de forma dinámica requieren unblocking="render"explícito. - SÍ usa
blocking="render"en elementos<script>que deben ejecutarse antes de que se anime la transición (p.ej., secuencias de comandos que aplican un tema o afectan el diseño). - SÍ usa
<link rel="expect" href="#element-id" blocking="render">para bloquear la renderización hasta que se analice el contenido de la mitad superior de la página. Esto se aplica a todos los tipos de transiciones: transiciones de página completa (para evitar la animación a una página en blanco), animaciones de transformación (para garantizar que existan elementos con nombre en el DOM) y diseños que dependen de secuencias de comandos (para garantizar que se analice el contenido con diseño). - NO bloquees la renderización en contenido no crítico. Bloquea solo los recursos y elementos que afectan el viewport inicial. Bloquear demasiado contenido retrasa la transición y degrada el rendimiento percibido.
Guía de implementación
Paso 1: Habilita las transiciones de vistas entre documentos
OBLIGATORIO: Tanto la página de origen como la de destino deben incluir la regla-at @view-transition. Sin esto, no se produce ninguna transición entre documentos.
/*
MANDATORY: Include this rule in every page that participates
in cross-document view transitions.
`navigation: auto` enables transitions for standard navigations
(link clicks, form submissions, back/forward).
*/
@view-transition {
navigation: auto;
}
/* MANDATORY Copy-Paste Safety: Disable cross-document view transitions for users requesting reduced motion */
@media (prefers-reduced-motion: reduce) {
@view-transition {
navigation: none;
}
}
Paso 2: Bloquea la renderización hasta que se ejecuten las secuencias de comandos críticas
Si una secuencia de comandos sin bloqueo en <head> debe ejecutarse antes de que se anime la transición (p.ej., para aplicar una clase de tema o afectar el diseño), márcala con blocking="render". Sin esto, las secuencias de comandos async, defer o type="module" pueden ejecutarse después de que ya haya comenzado la transición.
<head>
<!--
DO: Mark layout-critical scripts with blocking="render".
-->
<script type=module blocking="render">
// Example: apply a stored theme before the page renders,
// so the transition snapshot reflects the correct theme.
document.documentElement.dataset.theme =
localStorage.getItem('theme') || 'light';
</script>
</head>
Paso 3: Bloquea la renderización hasta que se analicen los elementos clave del DOM
Las hojas de estilo y las secuencias de comandos blocking="render" en <head> solo garantizan que se haya procesado por completo <head>. No esperan a que se analice ningún contenido de <body>. Sin un bloqueo adicional, el navegador puede tomar la instantánea de la página nueva antes de que existan elementos de la mitad superior de la página en el DOM, lo que genera una transición que se anima a una página en blanco o renderizada parcialmente.
<link rel="expect"> resuelve este problema bloqueando la renderización hasta que se analice un elemento específico (identificado por su id). El valor de href debe ser un identificador de fragmento (p.ej., #hero) que coincida con el atributo id del elemento de destino. Una vez que se analiza la etiqueta de cierre de ese elemento, se libera el bloqueo de renderización. Ten en cuenta que <link rel="expect"> solo garantiza que el elemento del DOM de destino exista en el documento; no espera a que se descarguen o decodifiquen los recursos secundarios externos (como los píxeles de <img>).
SÍ uses <link rel="expect"> en las siguientes situaciones:
Caso de uso 1: Transición gradual de página completa
Incluso cuando ningún elemento individual tiene un view-transition-name, la transición predeterminada de root realiza un fundido cruzado de toda la página. Si la instantánea de la página nueva se toma antes de que se analice el contenido de la mitad superior de la página, el fundido cruzado se anima desde la página anterior hasta una página en blanco o incompleta. Bloquea la renderización en un elemento que marque el final del contenido visible en la mitad superior de la página.
<head>
<link rel="stylesheet" href="/css/styles.css">
<!--
DO: Block rendering until the main content area is parsed,
even for a simple cross-fade. Without this, the browser may
snapshot the page before visible content exists in the DOM,
causing the cross-fade to reveal a blank or partial page.
-->
<link rel="expect" href="#main-content" blocking="render">
</head>
<body>
<header>...</header>
<main id="main-content">
<h1>Page Title</h1>
<p>Above-the-fold content the user should see immediately.</p>
</main>
<!-- Content below the fold does NOT need to be blocked on -->
<section>...</section>
</body>
Caso de uso 2: Animaciones de transformación entre elementos específicos
Cuando los elementos de ambas páginas comparten un view-transition-name, el navegador los transforma sin problemas durante la navegación. Si el elemento de destino no se analizó cuando comienza la transición, el navegador no puede encontrarlo: la transformación se degrada para separar las animaciones de salida y entrada. Bloquea la renderización hasta que se analice el elemento con el view-transition-name.
Debido a que <link rel="expect"> solo pausa la renderización hasta que se analiza el elemento del DOM de destino y no espera a que se descarguen o decodifiquen los datos de la imagen, es posible que un <img> no almacenado en caché se capture como un rectángulo vacío en la instantánea de la página nueva. Para evitar instantáneas en blanco o cambios de diseño durante las transformaciones de imágenes, precarga o decodifica previamente las imágenes de transición críticas (por ejemplo, con <link rel="preload" as="image"> o renderizando previamente la página de destino) y proporciona resguardos de tamaño explícitos (atributos width y height o CSS aspect-ratio, además de un color de fondo de marcador de posición).
<head>
<link rel="stylesheet" href="/css/styles.css">
<!--
DO: Preload critical transition images so their pixels are
available to decode before the new-page snapshot is captured.
<link rel="expect"> only waits for the DOM element to be parsed,
NOT for image pixels to download or decode.
-->
<link rel="preload" as="image" href="/img/product.webp" fetchpriority="high">
<!--
DO: Block rendering until the element participating in the
morph animation has been parsed. Without this, the browser
may start the transition before #hero exists, causing the
morph to degrade to a fade-out/fade-in.
-->
<link rel="expect" href="#hero" blocking="render">
<!--
When multiple blocking="render" resources are present,
rendering is blocked until ALL of them are satisfied.
Here, the browser waits for both the script to execute
AND the #hero element to be parsed — whichever comes last.
-->
<script async blocking="render" src="/js/transition-setup.js"></script>
</head>
<body>
<header>...</header>
<section id="hero">
<h1 style="view-transition-name: page-title">Product Name</h1>
<!--
DO: Provide explicit dimensions (width/height or aspect-ratio)
as a sizing fallback so the snapshot captures the correct layout
box even if image decoding is still in progress.
-->
<img
style="view-transition-name: hero-image"
src="/img/product.webp"
width="800"
height="600"
alt="Product"
>
</section>
</body>
Paso 4: Usa consultas de medios para el bloqueo de renderización responsivo
Los diferentes tamaños de viewport pueden mostrar diferentes cantidades de contenido en la mitad superior de la página. Usa el atributo media en <link rel="expect"> para bloquear la renderización solo del contenido visible en un ancho de viewport determinado.
<head>
<!--
DO: Use media queries to conditionally block rendering.
On wide screens, both the hero and the sidebar are visible,
so block until both are parsed. On narrow screens, only the
hero is visible initially.
-->
<link
rel="expect"
href="#hero"
blocking="render"
media="screen and (width <= 768px)"
>
<link
rel="expect"
href="#sidebar"
blocking="render"
media="screen and (width > 768px)"
>
</head>
Paso 5: Usa pagereveal para transiciones que dependen del contexto (opcional)
El evento pagereveal no es obligatorio para la estrategia principal de bloqueo de renderización. Solo se necesita cuando los valores de view-transition-name se deben asignar de forma dinámica según el lugar desde el que navegó el usuario, por ejemplo, para transformar un elemento de lista específico en un encabezado de página de detalles.
Si los valores de view-transition-name se asignan de forma estática en CSS o si solo usas el fundido cruzado predeterminado de página completa, omite este paso por completo.
<head>
<!--
MANDATORY: The pagereveal listener must be registered before
the page renders. Inline small setup scripts with blocking="render"
to avoid an extra network round-trip on cold loads, or use an
external async script with blocking="render" so the listener is
registered early without blocking HTML parsing.
If the listener is registered too late (e.g., in a deferred
script), the event may have already fired.
-->
<script async blocking="render" src="/js/transition-setup.js"></script>
</head>
// transition-setup.js
window.addEventListener('pagereveal', async (event) => {
if (!event.viewTransition) return;
const from = navigation.activation?.from;
if (!from) return;
const fromUrl = new URL(from.url);
// DO: Assign view-transition-name based on navigation context.
// This enables a morph animation from the product card on the
// list page to the heading on the detail page.
if (fromUrl.pathname === '/products/') {
const heading = document.querySelector('main h1');
if (!heading) return;
heading.style.viewTransitionName = 'product-title';
try {
await event.viewTransition.ready;
} catch (error) {
// Skipped view transitions reject `ready` with InvalidStateError.
if (error.name !== 'InvalidStateError') {
throw error;
}
} finally {
// MANDATORY: Remove the temporary name after the transition
// finishes. Stale names interfere with subsequent navigations
// and prevent the page from entering the bfcache.
await event.viewTransition.finished;
heading.style.viewTransitionName = '';
}
}
});
Prácticas recomendadas
- SÍ asignes
view-transition-namea través de CSS siempre que sea posible. Reserva la asignación de JavaScript (a través depagereveal) para los casos en los que el nombre dependa del contexto de navegación. - SÍ mantén las secuencias de comandos que bloquean la renderización pequeñas y rápidas. El navegador tiene un tiempo de espera integrado (alrededor de 4 segundos), después del cual se omite la transición por completo (se rechaza
viewTransition.readycon unInvalidStateErrorsi se inició una transición). - NO uses
<link rel="expect">para bloquear elementos que se encuentran en la parte inferior de la página y que no son visibles en el viewport inicial. Esto retrasa la transición sin ningún beneficio visual. - NO marques las herramientas de análisis, las redes de publicidad, los administradores de etiquetas ni otros cargadores de terceros con
blocking="render", incluso si insertan contenido en el viewport inicial. Su latencia de red se tiene en cuenta para el tiempo de espera de la transición, y su resultado no forma parte del estado estable de la página. - NO asignes el mismo
view-transition-namea varios elementos de la misma página. Los nombres duplicados hacen que se omita toda la transición. - Impacto en el tiempo de la tecnología de accesibilidad: El uso de
blocking="render"retrasa las actualizaciones visuales y el procesamiento de imagen inicial. Si bien esto evita fallas visuales para los usuarios videntes, puede causar latencia de procesamiento o inicialización diferida para los lectores de pantalla y otras tecnologías de accesibilidad que dependen de árboles de accesibilidad renderizados. Sopesa los beneficios de la continuidad visual frente a la latencia de lectura inicial para los usuarios no visuales y asegúrate de que las secuencias de comandos que bloquean la renderización sean mínimas y estén extremadamente optimizadas.
Estrategias de resguardo
Las transiciones de vistas entre documentos son una excelente opción para la mejora progresiva. En los navegadores que no las admiten, se ignora la regla @view-transition y se realizan navegaciones estándar del mismo origen exactamente como lo harían sin la función. Los navegadores compatibles obtienen transiciones fluidas, mientras que todos los demás obtienen una navegación estándar. La compatibilidad limitada con navegadores no es un motivo para evitar la adopción.
Todos los navegadores que admiten transiciones de vistas entre documentos también admiten blocking="render" y <link rel="expect">, por lo que no se necesita un resguardo independiente para las funciones de bloqueo de renderización que se describen en esta guía.
Otros factores que considerar
- Impacto en el rendimiento: Cada recurso que bloquea la renderización retrasa el inicio de la animación de transición de vista. Minimiza la cantidad de secuencias de comandos que bloquean la renderización y usa
<link rel="expect">solo para los elementos que se encuentran en la mitad superior de la página. Renderiza previamente las páginas de destino con la API de Speculation Rules para eliminar por completo los retrasos en la carga. - Comportamiento de tiempo de espera: Si el tiempo combinado de bloqueo de renderización supera los 4 segundos aproximadamente, el navegador omite la transición (y cualquier promesa
viewTransition.readyactiva se rechaza con unInvalidStateError). Asegúrate de que los recursos críticos se carguen bien dentro de este período. - Compatibilidad con bfcache: Las asignaciones temporales de
view-transition-nameque no se limpian después de la transición pueden impedir que la página ingrese a la bfcache. Quita siempre los nombres asignados de forma dinámica en la devolución de llamadafinished.