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 è in genere il caso delle applicazioni multipagina (MPA). Le transizioni tra le visualizzazioni dei documenti sono supportate in Chrome a partire dalla versione 126.

Supporto dei browser

  • Chrome: 126.
  • Edge: 126.
  • Firefox: non supportato.
  • Safari Technology Preview: 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 view-transition-name univoco sia nella pagina vecchia che in quella nuova.
  2. Il DOM viene aggiornato mentre il rendering viene soppresso.
  3. Infine, le transizioni sono basate sulle animazioni CSS.

La differenza rispetto alle transizioni di visualizzazione all'interno 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 la navigazione all'interno dello stesso dominio da una pagina all'altra, un'azione che in genere viene eseguita dall'utente del tuo sito web facendo 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 nella stessa origine.
  • Entrambe le pagine devono essere attivate per consentire la transizione di visualizzazione.

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


Le transizioni tra viste di documenti diversi sono limitate alle navigazioni all'interno della stessa origine

Le transizioni di visualizzazione tra documenti sono limitate solo alle navigazioni della 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 su web.dev.

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

Ad esempio, puoi avere una transizione di visualizzazione tra documenti quando passi da developer.chrome.com a developer.chrome.com/blog, poiché hanno lo stesso origine. Non puoi eseguire questa transizione quando navighi da developer.chrome.com a www.chrome.com, poiché si tratta di siti di origine diversi e nello stesso sito.


Le transizioni tra visualizzazioni di documenti sono facoltative

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

Nell'atrule @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;
}

Se imposti il descrittore navigation su auto, attivi le transizioni di visualizzazione per i seguenti valori 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 sono, ad esempio, la navigazione utilizzando la barra degli indirizzi URL o facendo clic su un preferito, nonché qualsiasi forma di ricarica avviata dall'utente o dallo script.

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

.

Demo sulle transizioni della visualizzazione tra documenti

Dai un'occhiata alla 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. È richiesta la versione 126 o successive di Chrome.

Personalizzare le transizioni tra le visualizzazioni dei 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 tra le visualizzazioni dei documenti, la specifica HTML include due nuovi eventi che puoi utilizzare: pageswap e pagereveal.

Questi due eventi vengono attivati per ogni navigazione tra documenti dello stesso dominio, indipendentemente dal fatto che stia per verificarsi o meno una transizione di visualizzazione. Se sta per avvenire una transizione di 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 degli snapshot precedenti.
  • L'evento pagereveal viene attivato in una pagina dopo che è stata inizializzata o riattivata, ma prima della prima opportunità di rendering. In questo modo, puoi personalizzare la nuova pagina prima che vengano acquisiti i nuovi istantanei.

Ad esempio, puoi utilizzare questi eventi per impostare o modificare rapidamente alcuni valori view-transition-name o passare dati da un documento all'altro scrivendo e leggendo i dati da sessionStorage per personalizzare la transizione di visualizzazione prima dell'esecuzione effettiva.

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 in modo diverso le varie promesse:

  • 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 anche intervenire in base agli URL delle pagine precedenti e nuove.

Ad esempio, in MPA Stack Navigator 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 scorrere da destra verso sinistra.
  • Quando passi dalla pagina dei dettagli alla pagina di 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.

A questo scopo, ora i browser possono esporre oggetti NavigationActivation che contengono informazioni sulla navigazione con la stessa origine. Questo oggetto espone il tipo di navigazione utilizzato, le voci della cronologia della destinazione corrente e finale come indicato in navigation.entries() dell'API Navigation.

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

Dai un'occhiata a questa demo di 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 di visualizzazione.

In questo modo, non devi decorare ogni 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 di 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';
    }
  }
});

Il codice esegue anche la pulizia dopo l'esecuzione rimuovendo i valori view-transition-name dopo la transizione di visualizzazione. In questo modo la pagina è pronta per le navigazioni successive e può anche gestire il trasferimento della cronologia.

Per aiutarti, 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 = '';
  }
}

Ora il codice precedente può 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);
    }
  }
});

Attendere il caricamento dei contenuti con il blocco del rendering

Supporto dei browser

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

In alcuni casi, potresti voler posticipare il primo rendering di una pagina finché non è presente un determinato elemento nel nuovo DOM. In questo modo si evitano lampi 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 del primo rendering 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 debbano essere caricati. Ad esempio, con le immagini, la sola presenza del tag <img> con il id specificato nella struttura DOM è sufficiente per la valutazione della condizione come vera. 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 valutare 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 passi alla pagina successiva o a quella precedente in una paginazione, ti consigliamo di utilizzare animazioni diverse a seconda che tu stia passando a una pagina più alta o più bassa della sequenza.

Per impostare questi tipi in anticipo, aggiungili nella regola at @view-transition:

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

Per impostare i tipi dinamicamente, 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. Per consentire l'esecuzione delle animazioni come previsto, devi determinare i tipi da utilizzare almeno nella nuova pagina.

Per rispondere a questi tipi, utilizza il selettore di pseudo-classi :active-view-transition-type() come per le transizioni di visualizzazione all'interno 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, vengono ripuliti automaticamente al termine di una transizione di visualizzazione. Per questo motivo, i tipi funzionano bene con funzionalità come BFCache.

Demo

Nella seguente demo di impaginazione, i contenuti della pagina scorrono in avanti o indietro in base al numero di pagina a cui stai passando.

Registrazione della demo Pagination (MPA). Utilizza transizioni diverse a seconda della pagina che stai visitando.

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

I feedback degli sviluppatori sono sempre apprezzati. Per condividere, segnala un problema al gruppo di lavoro CSS su GitHub con suggerimenti e domande. Prefissa il problema con [css-view-transitions]. Se riscontri un bug, segnala un bug di Chromium.