Transizioni della visualizzazione tra più documenti per le applicazioni con più pagine

Quando si verifica una transizione tra due documenti diversi, si parla di transizione della visualizzazione tra documenti. Questo avviene tipicamente nelle applicazioni multipagina (MPA). Le transizioni di visualizzazione tra documenti sono supportate in Chrome a partire dalla versione 126.

Supporto dei browser

  • Chrome: 126.
  • Edge: 126.
  • Firefox: non supportato.
  • Safari: non supportato.

Le transizioni di visualizzazione tra documenti si basano sugli stessi componenti di base e principi delle transizioni di visualizzazione stesso documento, il che è molto intenzionale:

  1. Il browser acquisisce istantanee degli elementi che hanno un valore view-transition-name univoco sia nella vecchia che nella nuova pagina.
  2. Il DOM viene aggiornato mentre il rendering viene soppresso.
  3. Infine, le transizioni sono basate su animazioni CSS.
di Gemini Advanced.

La differenza rispetto alle transizioni di visualizzazione dello stesso documento è che, con le transizioni di visualizzazione tra documenti, non è necessario chiamare document.startViewTransition per avviare una transizione di visualizzazione. L'attivatore di una transizione di visualizzazione tra documenti è invece una navigazione della stessa origine da una pagina all'altra, un'azione che in genere viene eseguita da un utente del tuo sito web che fa clic su un link.

In altre parole, non esiste un'API da chiamare per avviare una transizione di visualizzazione tra due documenti. Tuttavia, devono essere soddisfatte due condizioni:

  • Entrambi i documenti devono esistere sulla stessa origine.
  • Entrambe le pagine devono essere attivate per consentire la transizione della visualizzazione.

Entrambe queste condizioni sono illustrate più avanti in questo documento.


Le transizioni della visualizzazione tra documenti sono limitate alle navigazioni della stessa origine

Le transizioni di visualizzazione tra documenti sono limitate solo alle navigazioni dalla stessa origine. Una navigazione è considerata della stessa origine se l'origine di entrambe le pagine partecipanti è la stessa.

L'origine di una pagina è una combinazione dello schema, del nome host e della porta utilizzati, come descritto in dettaglio su web.dev.

Un URL di esempio con schema, nome host e porta evidenziati. che, combinati, formano l'origine.
Un URL di esempio con schema, nome host e porta evidenziati. che, combinati, formano l'origine.

Ad esempio, puoi avere una transizione della visualizzazione su più documenti durante la navigazione da developer.chrome.com a developer.chrome.com/blog, poiché sono della stessa origine. Non puoi avere questa transizione durante la navigazione da developer.chrome.com a www.chrome.com, poiché sono multiorigine e nello stesso sito.


Le transizioni di visualizzazione tra documenti sono attivabili

Per effettuare una transizione della visualizzazione tra documenti tra due documenti, entrambe le pagine partecipanti devono attivare questa opzione. Ciò viene fatto con la regola at @view-transition in CSS.

Nella regola at-rule @view-transition, imposta il descrittore navigation su auto per attivare le transizioni di visualizzazione per le navigazioni tra documenti della stessa origine.

@view-transition {
  navigation: auto;
}

Impostando il descrittore navigation su auto, attivi le transizioni della visualizzazione per i seguenti NavigationType:

  • traverse
  • push o replace, se l'attivazione non è stata avviata dall'utente tramite i meccanismi dell'interfaccia utente del browser.

Le navigazioni escluse da auto riguardano, ad esempio, la navigazione utilizzando la barra degli indirizzi dell'URL o i clic su un preferito, nonché qualsiasi tipo di ricaricamento avviato da utente o script.

Se una navigazione richiede troppo tempo (nel caso di Chrome più di quattro secondi), la transizione della visualizzazione viene saltata con un DOMException TimeoutError.

Demo sulle transizioni della visualizzazione tra documenti

Guarda la seguente demo che utilizza le transizioni di visualizzazione per creare una demo di Stack Navigator. Qui non ci sono chiamate a document.startViewTransition(), le transizioni di visualizzazione vengono attivate passando da una pagina all'altra.

Registrazione della demo di Stack Navigator. Richiede Chrome 126 o versioni successive.
.

Personalizzare le transizioni della visualizzazione tra documenti

Per personalizzare le transizioni della visualizzazione tra documenti, è possibile utilizzare alcune funzionalità della piattaforma web.

Queste funzionalità non fanno parte della specifica dell'API View Transizione in sé, ma sono progettate per essere utilizzate insieme.

Gli eventi pageswap e pagereveal

Supporto dei browser

  • Chrome: 124.
  • Edge: 124.
  • Firefox: non supportato.
  • Safari: non supportato.

Origine

Per consentirti di personalizzare le transizioni delle visualizzazioni tra documenti, la specifica HTML include due nuovi eventi che puoi utilizzare: pageswap e pagereveal.

Questi due eventi vengono attivati per ogni navigazione tra documenti della stessa origine, indipendentemente dal fatto che stia per verificarsi una transizione della visualizzazione. Se sta per verificarsi una transizione della visualizzazione tra le due pagine, puoi accedere all'oggetto ViewTransition utilizzando la proprietà viewTransition in questi eventi.

  • L'evento pageswap viene attivato prima del rendering dell'ultimo frame di una pagina. Puoi utilizzarlo per apportare alcune modifiche dell'ultimo minuto alla pagina in uscita, subito prima dell'acquisizione dei vecchi snapshot.
  • L'evento pagereveal viene attivato su una pagina dopo che è stata inizializzata o riattivata, ma prima della prima opportunità di rendering. che ti consente di personalizzare la nuova pagina prima che vengano creati i nuovi snapshot.

Ad esempio, puoi utilizzare questi eventi per impostare o modificare rapidamente alcuni valori view-transition-name o trasferire i dati da un documento all'altro scrivendo e leggendo i dati da sessionStorage per personalizzare la transizione della vista prima che venga eseguita.

let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
  if (event.target.tagName.toLowerCase() === 'a') return;
  lastClickX = event.clientX;
  lastClickY = event.clientY;
});

// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
  if (event.viewTransition && lastClick) {
    sessionStorage.setItem('lastClickX', lastClickX);
    sessionStorage.setItem('lastClickY', lastClickY);
  }
});

// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
  if (event.viewTransition) {
    lastClickX = sessionStorage.getItem('lastClickX');
    lastClickY = sessionStorage.getItem('lastClickY');
  }
});

Se vuoi, puoi decidere di saltare la transizione in entrambi gli eventi.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    if (goodReasonToSkipTheViewTransition()) {
      e.viewTransition.skipTransition();
    }
  }
}

L'oggetto ViewTransition in pageswap e pagereveal sono due oggetti diversi. Inoltre, gestiscono le varie promesse in modo diverso:

  • pageswap: una volta nascosto il documento, il vecchio oggetto ViewTransition viene ignorato. In questo caso, viewTransition.ready rifiuta e viewTransition.finished risolve.
  • pagereveal: la promessa updateCallBack è già stata risolta a questo punto. Puoi utilizzare le promesse viewTransition.ready e viewTransition.finished.

Supporto dei browser

  • Chrome: 123.
  • Edge: 123.
  • Firefox: non supportato.
  • Safari: non supportato.

Origine

In entrambi gli eventi, pageswap e pagereveal, puoi intervenire anche in base agli URL delle pagine nuove e precedenti.

Ad esempio, nello strumento di navigazione dello stack MPA, il tipo di animazione da utilizzare dipende dal percorso di navigazione:

  • Quando passi dalla pagina Panoramica a una pagina dei dettagli, i nuovi contenuti devono essere visualizzati da destra a sinistra.
  • Quando passi dalla pagina dei dettagli alla pagina Panoramica, i vecchi contenuti devono scorrere da sinistra a destra.

Per farlo, hai bisogno di informazioni sulla navigazione che, nel caso di pageswap, sta per verificarsi o, nel caso di pagereveal, è appena avvenuta.

Per farlo, i browser ora possono esporre gli oggetti NavigationActivation che contengono informazioni sulla navigazione della stessa origine. Questo oggetto mostra il tipo di navigazione utilizzato, le voci della cronologia della destinazione corrente e quella finale, come si trova in navigation.entries() dall'API Navigation.

Su una pagina attivata, puoi accedere a questo oggetto tramite navigation.activation. Nell'evento pageswap, puoi accedere tramite e.activation.

Guarda questa demo sui profili che utilizza le informazioni NavigationActivation negli eventi pageswap e pagereveal per impostare i valori view-transition-name sugli elementi che devono partecipare alla transizione della visualizzazione.

In questo modo, non dovrai decorare ogni singolo elemento dell'elenco con un view-transition-name in anticipo. Questo avviene just-in-time, tramite JavaScript, solo per gli elementi che lo richiedono.

Registrazione della demo dei profili. Richiede Chrome 126 o versioni successive.
.

Il codice è il seguente:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove view-transition-names after snapshots have been taken
      // (this to deal with BFCache)
      await e.viewTransition.finished;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove names after snapshots have been taken
      // so that we're ready for the next navigation
      await e.viewTransition.ready;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

Inoltre, il codice si pulisce automaticamente rimuovendo i valori view-transition-name dopo l'esecuzione della transizione della visualizzazione. In questo modo la pagina è pronta per le navigazioni successive e può anche gestire il trasferimento della cronologia.

A tal fine, utilizza questa funzione di utilità che imposta temporaneamente i view-transition-name.

const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = name;
  }

  await vtPromise;

  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = '';
  }
}

Il codice precedente può ora essere semplificato come segue:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      // Clean up after the page got replaced
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.finished);
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      // Clean up after the snapshots have been taken
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.ready);
    }
  }
});

Attendi il caricamento dei contenuti con il blocco della visualizzazione

Supporto dei browser

  • Chrome: 124.
  • Edge: 124.
  • Firefox: non supportato.
  • Safari: non supportato.

In alcuni casi potresti voler aspettare la prima visualizzazione di una pagina finché non è presente un determinato elemento nel nuovo DOM. In questo modo si evita il flashing e si assicura che lo stato a cui stai animando sia stabile.

In <head>, definisci uno o più ID elemento che devono essere presenti prima che venga eseguita la prima visualizzazione della pagina, utilizzando il seguente meta tag.

<link rel="expect" blocking="render" href="#section1">

Questo meta tag indica che l'elemento deve essere presente nel DOM, non che i contenuti devono essere caricati. Ad esempio, nel caso delle immagini, la sola presenza del tag <img> con il valore id specificato nell'albero DOM è sufficiente affinché la condizione restituisca true. L'immagine stessa potrebbe essere ancora in fase di caricamento.

Prima di proseguire con il blocco del rendering, tieni presente che il rendering incrementale è un aspetto fondamentale del Web, quindi fai attenzione quando scegli di bloccare il rendering. L'impatto del blocco del rendering deve essere valutato caso per caso. Per impostazione predefinita, evita di utilizzare blocking=render, a meno che tu non possa misurare e misurare attivamente l'impatto che ha sui tuoi utenti misurando l'impatto sui tuoi Core Web Vitals.


Visualizza i tipi di transizione nelle transizioni di visualizzazione tra documenti

Le transizioni di visualizzazione tra documenti supportano anche i tipi di transizione della visualizzazione per personalizzare le animazioni e gli elementi acquisiti.

Ad esempio, quando si passa alla pagina successiva o precedente di un'impaginazione, è possibile utilizzare animazioni diverse a seconda che si acceda a una pagina superiore o inferiore della sequenza.

Per impostare questi tipi in anticipo, aggiungi i tipi nella regola at-rule @view-transition:

@view-transition {
  navigation: auto;
  types: slide, forwards;
}

Per impostare i tipi al volo, utilizza gli eventi pageswap e pagereveal per manipolare il valore di e.viewTransition.types.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
    e.viewTransition.types.add(transitionType);
  }
});

I tipi non vengono trasferiti automaticamente dall'oggetto ViewTransition della vecchia pagina all'oggetto ViewTransition della nuova pagina. Devi determinare i tipi da utilizzare almeno nella nuova pagina affinché le animazioni vengano eseguite come previsto.

Per rispondere a questi tipi di problemi, usa il selettore di pseudo-classe :active-view-transition-type() come faresti con le transizioni di visualizzazione dello stesso documento

/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
  :root {
    view-transition-name: none;
  }
  article {
    view-transition-name: content;
  }
  .pagination {
    view-transition-name: pagination;
  }
}

/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-left;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-right;
  }
}

/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-right;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-left;
  }
}

/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
  &::view-transition-old(root) {
    animation-name: fade-out, scale-down;
  }
  &::view-transition-new(root) {
    animation-delay: 0.25s;
    animation-name: fade-in, scale-up;
  }
}

Poiché i tipi si applicano solo a una transizione di Visualizzazione attiva, la pulizia dei tipi viene eseguita automaticamente al termine di una transizione di visualizzazione. Per questo motivo, i tipi funzionano bene con funzionalità come BFCache.

Demo

Nella seguente demo di paginazione, i contenuti della pagina scorrono in avanti o indietro in base al numero di pagina che stai visualizzando.

Registrazione della demo di Impaginazione (MPA). Utilizza transizioni diverse a seconda della pagina che vuoi visitare.

Il tipo di transizione da utilizzare viene determinato negli eventi pagereveal e pageswap esaminando gli URL di destinazione e uscita.

const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
  const currentURL = new URL(fromNavigationEntry.url);
  const destinationURL = new URL(toNavigationEntry.url);

  const currentPathname = currentURL.pathname;
  const destinationPathname = destinationURL.pathname;

  if (currentPathname === destinationPathname) {
    return "reload";
  } else {
    const currentPageIndex = extractPageIndexFromPath(currentPathname);
    const destinationPageIndex = extractPageIndexFromPath(destinationPathname);

    if (currentPageIndex > destinationPageIndex) {
      return 'backwards';
    }
    if (currentPageIndex < destinationPageIndex) {
      return 'forwards';
    }

    return 'unknown';
  }
};

Feedback

Il feedback degli sviluppatori è sempre apprezzato. Per condividere il file, invia una segnalazione al CSS Working Group su GitHub con suggerimenti e domande. Fai precedere il problema da [css-view-transitions]. Se riscontri un bug, segnala un bug di Chromium.