Transizioni tra documenti coerenti

Il problema

Le transizioni di visualizzazione tra documenti animano gli elementi tra due pagine durante una navigazione della stessa origine. Il browser acquisisce un'istantanea della pagina precedente, naviga e poi anima dall'istantanea alla nuova pagina. Se la nuova pagina non ha terminato il caricamento delle risorse critiche (fogli di stile, script di layout o elementi DOM chiave), la transizione viene animata in uno stato incompleto o senza stile. Ciò causa problemi visivi, come elementi che si spostano in posizioni errate, contenuti che si adattano dinamicamente a metà animazione o caratteri di riserva che lampeggiano sui caratteri web al termine della transizione.

La soluzione

Utilizza blocking="render" sugli elementi <link> e <script> critici nel <head> della nuova pagina e utilizza <link rel="expect"> per bloccare il rendering fino a quando non vengono analizzati elementi DOM specifici. In questo modo, il browser non avvia l'animazione della transizione di visualizzazione finché lo stato visivo della nuova pagina non è stabile. Il browser continua ad analizzare l'HTML in background, mentre solo la visualizzazione viene posticipata.

Strategia di implementazione

  1. OBBLIGATORIO: attiva le transizioni di visualizzazione tra documenti con la regola CSS at-rule @view-transition su entrambe le pagine.
  2. OBBLIGATORIO: assicurati che i fogli di stile critici siano presenti nella sezione <head>. I fogli di stile in <head> bloccano il rendering per impostazione predefinita. I fogli di stile inseriti dinamicamente richiedono un blocking="render" esplicito.
  3. SÌ: utilizza blocking="render" su elementi <script> che devono essere eseguiti prima dell'animazione della transizione (ad es. script che applicano un tema o influiscono sul layout).
  4. SÌ: utilizza <link rel="expect" href="#element-id" blocking="render"> per bloccare il rendering finché i contenuti above the fold non sono stati analizzati. Questo vale per tutti i tipi di transizione: dissolvenze incrociate a pagina intera (per evitare l'animazione su una pagina vuota), animazioni di morphing (per garantire che gli elementi con nome esistano nel DOM) e layout dipendenti da script (per garantire che i contenuti con stile vengano analizzati).
  5. NON bloccare il rendering dei contenuti non critici. Blocca solo le risorse e gli elementi che influiscono sulla visualizzazione iniziale. Il blocco di troppi contenuti ritarda la transizione e peggiora le prestazioni percepite.

Guida all'implementazione

Passaggio 1: attiva le transizioni di visualizzazione tra documenti

OBBLIGATORIO: sia la pagina di origine che quella di destinazione devono includere la regola @ @view-transition. Senza questo, non si verifica alcuna transizione tra documenti.

/*
  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;
  }
}

Passaggio 2: blocca il rendering fino all'esecuzione degli script critici

Se uno script non bloccante in <head> deve essere eseguito prima dell'animazione della transizione (ad esempio per applicare una classe di tema o influire sul layout), contrassegnalo con blocking="render". In caso contrario, gli script async, defer o type="module" potrebbero essere eseguiti dopo l'inizio della transizione.

<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>

Passaggio 3: blocca il rendering fino all'analisi degli elementi DOM chiave

I fogli di stile e gli script blocking="render" nella sezione <head> garantiscono solo che la sezione <head> sia stata completamente elaborata. Non attendono l'analisi di alcun contenuto <body>. Senza un blocco aggiuntivo, il browser potrebbe acquisire l'istantanea della nuova pagina prima che gli elementi above the fold esistano nel DOM, il che comporta una transizione che anima una pagina vuota o parzialmente visualizzata.

<link rel="expect"> risolve questo problema bloccando il rendering fino a quando non viene analizzato un elemento specifico (identificato dal relativo id). Il valore href deve essere un identificatore di frammento (ad es. #hero) corrispondente all'attributo id dell'elemento di destinazione. Una volta analizzato il tag di chiusura dell'elemento, il blocco di rendering viene rilasciato. Tieni presente che <link rel="expect"> garantisce solo che l'elemento DOM di destinazione esista nel documento, ma non attende il download o la decodifica delle risorse secondarie esterne (come i pixel <img>).

USA <link rel="expect"> in tutti i seguenti scenari:

Caso d'uso 1: dissolvenza incrociata a pagina intera

Anche se nessun singolo elemento ha un view-transition-name, la transizione root predefinita applica la dissolvenza incrociata all'intera pagina. Se l'istantanea della nuova pagina viene acquisita prima che i contenuti above the fold vengano analizzati, la dissolvenza incrociata viene animata dalla pagina precedente a una pagina vuota o incompleta. Blocca il rendering su un elemento che contrassegna la fine dei contenuti visibili above the fold.

<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 d'uso 2: animazioni di metamorfosi tra elementi specifici

Quando gli elementi di entrambe le pagine condividono un view-transition-name, il browser li trasforma in modo fluido durante la navigazione. Se l'elemento di destinazione non è stato analizzato all'inizio della transizione, il browser non riesce a trovarlo e il morphing si riduce ad animazioni di uscita e di entrata separate. Blocca il rendering finché l'elemento con view-transition-name non è stato analizzato.

Poiché <link rel="expect"> mette in pausa il rendering solo fino all'analisi dell'elemento DOM di destinazione e non attende il download o la decodifica dei dati dell'immagine, un <img> non memorizzato nella cache potrebbe essere acquisito come un rettangolo vuoto nello snapshot della nuova pagina. Per evitare snapshot vuoti o variazioni del layout durante le trasformazioni delle immagini, precarica o predecodifica le immagini di transizione critiche (ad esempio con <link rel="preload" as="image"> o prerenderizzando la pagina di destinazione) e fornisci fallback di dimensionamento espliciti (attributi width e height o CSS aspect-ratio, oltre a un colore di sfondo segnaposto).

<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>

Passaggio 4: utilizza le query supporti per il blocco della visualizzazione adattabile

Le diverse dimensioni dell'area visibile possono mostrare quantità diverse di contenuti above the fold. Utilizza l'attributo media su <link rel="expect"> per bloccare il rendering solo per i contenuti visibili in una determinata larghezza dell'area visibile.

<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>

Passaggio 5: utilizza pagereveal per le transizioni dipendenti dal contesto (facoltativo)

L'evento pagereveal non è obbligatorio per la strategia di blocco del rendering di base. È necessario solo quando i valori view-transition-name devono essere assegnati in modo dinamico in base alla pagina da cui l'utente ha eseguito la navigazione, ad esempio per trasformare un elemento di un elenco specifico in un'intestazione della pagina dei dettagli.

Se i valori view-transition-name sono assegnati in modo statico nel CSS o se utilizzi solo la dissolvenza incrociata predefinita a pagina intera, salta completamente questo passaggio.

<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 = '';
    }
  }
});

Best practice

  • ASSEGNA view-transition-name tramite CSS ogni volta che è possibile. Riserva l'assegnazione JavaScript (tramite pagereveal) ai casi in cui il nome dipende dal contesto di navigazione.
  • SÌ: mantieni gli script che bloccano il rendering piccoli e veloci. Il browser ha un timeout integrato (circa 4 secondi), dopo il quale la transizione viene saltata completamente (rifiutando viewTransition.ready con un InvalidStateError se è stata avviata una transizione).
  • NON utilizzare <link rel="expect"> per bloccare elementi in profondità nella pagina che non sono visibili nell'area visibile iniziale. Questo ritarda la transizione senza alcun vantaggio visivo.
  • NON contrassegnare con blocking="render" i caricatori di analisi, reti pubblicitarie, gestione tag o altri caricatori di terze parti, anche se inseriscono contenuti nell'area visibile iniziale. La loro latenza di rete viene conteggiata ai fini del timeout della transizione e il loro output non fa parte dello stato stabile della pagina.
  • NON assegnare lo stesso view-transition-name a più elementi della stessa pagina. I nomi duplicati causano il salto dell'intera transizione.
  • Impatto sulle tempistiche delle tecnologie per la disabilità: l'utilizzo di blocking="render" ritarda gli aggiornamenti visivi e la visualizzazione iniziale. Sebbene ciò eviti problemi visivi per gli utenti vedenti, può causare latenza di elaborazione o inizializzazione ritardata per gli screen reader e altre tecnologie per la disabilità che dipendono da alberi di accessibilità visualizzati. Valuta i vantaggi della continuità visiva rispetto alla latenza di lettura iniziale per gli utenti non visivi e assicurati che gli script di blocco del rendering siano minimi ed estremamente ottimizzati.

Strategie di fallback

Le transizioni di visualizzazione tra documenti sono un ottimo candidato per il potenziamento progressivo. Nei browser che non la supportano, la regola @view-transition viene ignorata e le navigazioni standard della stessa origine avvengono esattamente come se la funzionalità non fosse presente. I browser supportati ottengono transizioni fluide; tutti gli altri ottengono la navigazione standard. Il supporto limitato dei browser non è un motivo per evitare l'adozione.

Tutti i browser che supportano le transizioni di visualizzazione tra documenti supportano anche blocking="render" e <link rel="expect">, pertanto non è necessario un fallback separato per le funzionalità di blocco del rendering descritte in questa guida.

Altre considerazioni

  1. Impatto sul rendimento: ogni risorsa che blocca il rendering ritarda l'avvio dell'animazione della transizione di visualizzazione. Riduci al minimo il numero di script che bloccano il rendering e utilizza <link rel="expect"> solo per gli elementi above the fold. Esegui il prerendering delle pagine di destinazione utilizzando l'API Speculation Rules per eliminare completamente i ritardi di caricamento.
  2. Comportamento di timeout: se il tempo di blocco della visualizzazione combinato supera circa 4 secondi, il browser salta la transizione (e qualsiasi promessa viewTransition.ready attiva viene rifiutata con un InvalidStateError). Assicurati che le risorse critiche vengano caricate entro questa finestra.
  3. Compatibilità con bfcache: le assegnazioni temporanee di view-transition-name che non vengono rimosse dopo la transizione possono impedire alla pagina di entrare nella bfcache. Rimuovi sempre i nomi assegnati dinamicamente nel callback finished.