Quando una transizione di visualizzazione avviene tra due documenti diversi, si parla di transizione di 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
Le transizioni tra visualizzazioni di documenti diversi si basano sugli stessi elementi costitutivi e principi delle transizioni tra visualizzazioni dello stesso documento, il che è molto intenzionale:
- Il browser acquisisce istantanee degli elementi che hanno un
view-transition-name
univoco sia nella pagina vecchia che in quella nuova. - Il DOM viene aggiornato mentre il rendering viene soppresso.
- 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 della stessa origine 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 tra viste di documenti diversi sono limitate solo alle navigazioni con la 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.
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 dominio.
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 consentire la transizione 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
oreplace
, 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 delle transizioni tra visualizzazioni di documenti
Dai un'occhiata alla seguente demo che utilizza le transizioni di visualizzazione per creare una demo di Stack Navigator. Non sono presenti chiamate a document.startViewTransition()
, le transizioni di visualizzazione vengono attivate passando da una pagina all'altra.
Personalizzare le transizioni tra le visualizzazioni dei documenti
Per personalizzare le transizioni tra le visualizzazioni dei documenti, puoi utilizzare alcune funzionalità della piattaforma web.
Queste funzionalità non fanno parte della specifica dell'API View Transition, ma sono progettate per essere utilizzate in combinazione con essa.
Gli eventi pageswap
e pagereveal
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 che vengano acquisiti gli 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, l'oggettoViewTransition
precedente viene ignorato. In questo caso,viewTransition.ready
rifiuta eviewTransition.finished
risolve.pagereveal
: la promessaupdateCallBack
è già risolta a questo punto. Puoi utilizzare le promesseviewTransition.ready
eviewTransition.finished
.
Informazioni sull'attivazione della navigazione
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 avvenire 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. Ciò avviene invece in tempo reale utilizzando JavaScript, solo negli elementi che ne hanno bisogno.
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 l'esplorazione 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
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 adottare 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 sui tuoi utenti, misurando l'impatto sui Core Web Vitals.
Visualizzare i tipi di transizione nelle transizioni tra visualizzazioni di documenti
Le transizioni tra visualizzazioni di documenti supportano anche i tipi di transizione di visualizzazione per personalizzare le animazioni e gli elementi da acquisire.
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.
Il tipo di transizione da utilizzare viene determinato negli eventi pagereveal
e pageswap
esaminando gli URL di destinazione e di origine.
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.