O problema
As transições de visualização entre documentos animam elementos entre duas páginas durante uma navegação de mesma origem. O navegador captura um snapshot da página antiga, navega e, em seguida, anima do snapshot para a nova página. Se a nova página não tiver terminado de carregar recursos essenciais, como folhas de estilo, scripts de layout ou elementos DOM importantes, a transição será animada para um estado incompleto ou sem estilo. Isso causa falhas visuais, como elementos que se transformam em posições erradas, refluxo de conteúdo no meio da animação ou fontes substitutas que piscam para fontes da Web após a conclusão da transição.
A solução
Use blocking="render" em elementos <link> e <script> críticos no <head> da nova página e use <link rel="expect"> para bloquear a renderização até que elementos DOM específicos sejam analisados. Isso garante que o navegador não inicie a animação de transição de visualização até que o estado visual da nova página esteja estável. O navegador continua analisando o HTML em segundo plano. Somente a pintura é adiada.
Estratégia de implementação
- OBRIGATÓRIO: ative as transições de visualização entre documentos com a regra @ do CSS
@view-transitionnas duas páginas. - OBRIGATÓRIO: verifique se as folhas de estilo críticas estão no
<head>. As folhas de estilo no<head>bloqueiam a renderização por padrão. As folhas de estilo injetadas dinamicamente exigemblocking="render"explícito. - USE
blocking="render"em elementos<script>que precisam ser executados antes da animação da transição (por exemplo, scripts que aplicam um tema ou afetam o layout). - USE
<link rel="expect" href="#element-id" blocking="render">para bloquear a renderização até que o conteúdo acima da dobra seja analisado. Isso se aplica a todos os tipos de transição: cross-fades de página inteira (para evitar a animação em uma página em branco), animações de transformação (para garantir que os elementos nomeados existam no DOM) e layouts dependentes de script (para garantir que o conteúdo estilizado seja analisado). - NÃO bloqueie a renderização de conteúdo não essencial. Bloqueie apenas recursos e elementos que afetam a janela de visualização inicial. Bloquear muito conteúdo atrasa a transição e prejudica o desempenho percebido.
Guia de implementação
Etapa 1: ativar as transições de visualização entre documentos
OBRIGATÓRIO: as páginas de origem e de destino precisam incluir a regra @ @view-transition. Sem isso, nenhuma transição entre documentos ocorre.
/*
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;
}
}
Etapa 2: bloquear a renderização até que os scripts críticos sejam executados
Se um script não bloqueante no <head> precisar ser executado antes da animação da transição (por exemplo, para aplicar uma classe de tema ou afetar o layout), marque-o com blocking="render". Sem isso, os scripts async, defer ou type="module" podem ser executados depois que a transição já tiver começado.
<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>
Etapa 3: bloquear a renderização até que os elementos DOM principais sejam analisados
As folhas de estilo e os scripts blocking="render" no <head> só garantem que o <head> foi totalmente processado. Eles não esperam que o conteúdo de <body> seja analisado. Sem um bloqueio adicional, o navegador pode tirar o snapshot da nova página antes que os elementos acima da dobra existam no DOM, resultando em uma transição que anima para uma página em branco ou parcialmente renderizada.
O <link rel="expect"> resolve isso bloqueando a renderização até que um elemento específico (identificado pelo id) seja analisado. O valor de href precisa ser um identificador de fragmento (por exemplo, #hero) que corresponda ao atributo id do elemento de destino. Depois que a tag de fechamento desse elemento é analisada, o bloco de renderização é liberado. O <link rel="expect"> só garante que o elemento DOM de destino existe no documento. Ele não espera que sub-recursos externos (como pixels <img>) sejam baixados ou decodificados.
USE <link rel="expect"> em todos os seguintes cenários:
Caso de uso 1: transição gradual de página inteira
Mesmo quando nenhum elemento individual tem um view-transition-name, a transição padrão root faz um cross-fade na página inteira. Se o snapshot da nova página for tirado antes que o conteúdo acima da dobra seja analisado, o cross-fade vai animar da página antiga para uma página em branco ou incompleta. Bloqueie a renderização em um elemento que marca o fim do conteúdo visível acima da dobra.
<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: animações de transformação entre elementos específicos
Quando os elementos em ambas as páginas compartilham um view-transition-name, o navegador os transforma de forma suave durante a navegação. Se o elemento de destino não tiver sido analisado quando a transição começar, o navegador não vai conseguir encontrá-lo. A transformação vai se degradar em animações de saída e entrada separadas. Bloqueie a renderização até que o elemento com o view-transition-name seja analisado.
Como o <link rel="expect"> só pausa a renderização até que o elemento DOM de destino seja analisado e não espera o download ou a decodificação dos dados da imagem, um <img> não armazenado em cache pode ser capturado como um retângulo vazio no snapshot da nova página. Para evitar snapshots em branco ou mudanças de layout durante as transformações de imagem, pré-carregue ou pré-decodifique imagens de transição críticas (por exemplo, com <link rel="preload" as="image"> ou pré-renderizando a página de destino) e forneça substitutos de dimensionamento explícitos (atributos width e height ou CSS aspect-ratio, além de uma cor de fundo de marcador de posição).
<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>
Etapa 4: usar consultas de mídia para bloqueio de renderização responsivo
Diferentes tamanhos de janela de visualização podem mostrar diferentes quantidades de conteúdo acima da dobra. Use o atributo media em <link rel="expect"> para bloquear a renderização apenas do conteúdo visível em uma determinada largura de janela de visualização.
<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>
Etapa 5: usar pagereveal para transições dependentes de contexto (opcional)
O evento pagereveal não é obrigatório para a estratégia principal de bloqueio de renderização. Ele só é necessário quando os valores de view-transition-name precisam ser atribuídos dinamicamente com base no local de onde o usuário navegou. Por exemplo, transformar um item de lista específico em um título de página de detalhes.
Se os valores de view-transition-name forem atribuídos estaticamente no CSS ou se você estiver usando apenas o cross-fade padrão de página inteira, pule esta etapa.
<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áticas recomendadas
- ATRIBUA
view-transition-namevia CSS sempre que possível. Reserve a atribuição de JavaScript (viapagereveal) para casos em que o nome depende do contexto de navegação. - Mantenha os scripts que bloqueiam a renderização pequenos e rápidos. O navegador tem um tempo limite integrado (cerca de 4 segundos). Depois disso, a transição é totalmente ignorada (rejeitando
viewTransition.readycom umInvalidStateErrorse uma transição foi iniciada). - NÃO use
<link rel="expect">para bloquear elementos que estão no fundo da página e não aparecem na janela de visualização inicial. Isso atrasa a transição sem benefício visual. - NÃO marque carregadores de análise, rede de publicidade, gerenciador de tags ou outros carregadores de terceiros com
blocking="render", mesmo que eles injetem conteúdo na janela de visualização inicial. A latência de rede deles é contabilizada no tempo limite de transição, e a saída deles não faz parte do estado estável da página. - NÃO atribua o mesmo
view-transition-namea vários elementos na mesma página. Nomes duplicados fazem com que toda a transição seja ignorada. - Impacto no tempo da tecnologia adaptativa: o uso de
blocking="render"atrasa as atualizações visuais e a pintura inicial. Embora isso evite falhas visuais para usuários com visão, pode causar latência de processamento ou inicialização adiada para leitores de tela e outras tecnologias adaptativas que dependem de árvores de acessibilidade renderizadas. Compare os benefícios da continuidade visual com a latência de leitura inicial para usuários não visuais e garanta que os scripts que bloqueiam a renderização sejam mínimos e extremamente otimizados.
Estratégias de fallback
As transições de visualização entre documentos são uma excelente candidata para o aprimoramento progressivo. Em navegadores que não oferecem suporte a eles, a regra @view-transition é ignorada, e as navegações padrão de mesma origem ocorrem exatamente como ocorreriam sem o recurso. Os navegadores compatíveis recebem transições suaves, e os demais recebem a navegação padrão. O suporte limitado do navegador não é um motivo para evitar a adoção.
Todos os navegadores que oferecem suporte a transições de visualização entre documentos também oferecem suporte a blocking="render" e <link rel="expect">. Portanto, não é necessário um substituto separado para os recursos de bloqueio de renderização descritos neste guia.
Outras considerações
- Impacto na performance: cada recurso que bloqueia a renderização atrasa o início da animação de transição de visualização. Minimize o número de scripts que bloqueiam a renderização e use
<link rel="expect">apenas para elementos que estão acima da dobra. Pré-renderize as páginas de destino usando a API Speculation Rules para eliminar completamente os atrasos de carregamento. - Comportamento de tempo limite: se o tempo combinado de bloqueio de renderização exceder aproximadamente 4 segundos, o navegador vai pular a transição (e qualquer promessa
viewTransition.readyativa será rejeitada com umInvalidStateError). Verifique se os recursos críticos são carregados dentro dessa janela. - Compatibilidade com o bfcache: atribuições temporárias de
view-transition-nameque não são limpas após a transição podem impedir que a página entre no bfcache. Sempre remova os nomes atribuídos dinamicamente no callbackfinished.