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
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:
- Il browser acquisisce istantanee degli elementi che hanno un valore
view-transition-name
univoco sia nella vecchia che nella nuova pagina. - Il DOM viene aggiornato mentre il rendering viene soppresso.
- Infine, le transizioni sono basate su animazioni CSS.
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.
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
oreplace
, 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.
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
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 oggettoViewTransition
viene ignorato. In questo caso,viewTransition.ready
rifiuta eviewTransition.finished
risolve.pagereveal
: la promessaupdateCallBack
è già stata risolta a questo punto. Puoi utilizzare le promesseviewTransition.ready
eviewTransition.finished
.
Informazioni di attivazione della navigazione
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.
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
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.
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.