Quando una transizione di visualizzazione si verifica tra due documenti diversi, viene chiamata transizione di visualizzazione tra documenti. In genere è così nelle applicazioni multipagina (MPA). Le transizioni di visualizzazione tra documenti sono supportate in Chrome a partire dalla versione 126.
Le transizioni di visualizzazione tra documenti si basano sugli stessi blocchi costitutivi e principi delle transizioni di visualizzazione nello stesso documento, il che è molto intenzionale:
- Il browser acquisisce snapshot degli elementi che hanno un
view-transition-nameunivoco sia nella pagina precedente che in quella nuova. - Il DOM viene aggiornato mentre il rendering è soppresso.
- Infine, le transizioni sono basate su animazioni CSS.
A differenza delle transizioni di visualizzazione nello stesso documento, 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 con stessa origine da una pagina all'altra, un'azione in genere 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 della visualizzazione.
Entrambe queste condizioni sono spiegate più avanti in questo documento.
Le transizioni di visualizzazione tra documenti sono limitate alle navigazioni 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 di schema, nome host e porta utilizzati, come descritto su web.dev.
Ad esempio, puoi avere una transizione di visualizzazione tra documenti quando navighi da developer.chrome.com a developer.chrome.com/blog, in quanto hanno la stessa origine.
Non puoi avere questa transizione quando navighi da developer.chrome.com a www.chrome.com, in quanto si tratta di origini diverse e dello stesso sito.
Le transizioni di visualizzazione tra documenti sono facoltative
Per avere una transizione di visualizzazione tra due documenti, entrambe le pagine partecipanti devono attivare questa opzione. Questa operazione viene eseguita con la regola @@view-transition in CSS.
Nella regola @@view-transition, imposta il descrittore navigation su auto per attivare le transizioni di visualizzazione per le navigazioni multiorigine e stessa origine.
@view-transition {
navigation: auto;
}
Se imposti il descrittore navigation su auto, accetti che le transizioni di visualizzazione vengano eseguite per i seguenti NavigationType:
traversepushoreplace, se l'attivazione non è stata avviata dall'utente tramite i meccanismi della UI del browser.
Le navigazioni escluse da auto sono, ad esempio, la navigazione tramite la barra degli indirizzi URL o il clic su un segnalibro, nonché qualsiasi forma di ricaricamento avviato dall'utente o da uno script.
Se una navigazione richiede troppo tempo (più di quattro secondi nel caso di Chrome), la transizione Visualizzazione viene ignorata con un TimeoutError DOMException.
Demo delle transizioni di visualizzazione tra documenti
Dai un'occhiata alla seguente demo che utilizza le transizioni di visualizzazione per creare una demo di Stack Navigator. Qui non sono presenti chiamate a document.startViewTransition(), le transizioni di visualizzazione vengono attivate passando da una pagina all'altra.
Personalizzare le transizioni di visualizzazione tra documenti
Per personalizzare le transizioni di visualizzazione tra documenti, puoi utilizzare alcune funzionalità della piattaforma web.
- Gli eventi
pageswapepagereveal - Informazioni sull'attivazione della navigazione
- Blocco del rendering
Queste funzionalità non fanno parte delle specifiche 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 di visualizzazione tra documenti, la specifica HTML include due nuovi eventi che puoi utilizzare: pageswap e pagereveal.
Questi due eventi vengono attivati per ogni navigazione cross-documento della stessa origine, indipendentemente dal fatto che stia per verificarsi o meno una transizione di visualizzazione. Se sta per verificarsi una transizione di visualizzazione tra le due pagine, puoi accedere all'oggetto ViewTransition utilizzando la proprietà viewTransition in questi eventi.
- L'evento
pageswapviene attivato prima del rendering dell'ultimo frame di una pagina. Puoi utilizzarlo per apportare modifiche dell'ultimo minuto alla pagina in uscita, subito prima che vengano acquisite le vecchie istantanee. - L'evento
pagerevealviene attivato su una pagina dopo l'inizializzazione o la riattivazione, ma prima della prima opportunità di rendering. Con questa funzionalità, puoi personalizzare la nuova pagina prima che vengano acquisiti i nuovi snapshot.
Ad esempio, puoi utilizzare questi eventi per impostare o modificare rapidamente alcuni valori di view-transition-name o trasferire dati da un documento a un altro scrivendo e leggendo dati da sessionStorage per personalizzare la transizione della visualizzazione 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. Gestiscono anche le varie promesse in modo diverso:
pageswap: Una volta nascosto il documento, il vecchio oggettoViewTransitionviene ignorato. In questo caso,viewTransition.readyrifiuta eviewTransition.finishedrisolve.pagereveal: la promessaupdateCallBackè già stata risolta. Puoi utilizzare le promesseviewTransition.readyeviewTransition.finished.
Informazioni sull'attivazione della navigazione
Negli 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 di panoramica a una pagina dei dettagli, i nuovi contenuti devono scorrere da destra a sinistra.
- Quando si passa 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, i browser ora 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 di quella finale come si trovano in navigation.entries() dell'API Navigation.
In una pagina attivata, puoi accedere a questo oggetto tramite navigation.activation. Nell'evento pageswap, puoi accedere a questa funzionalità tramite e.activation.
Dai un'occhiata a questa demo di Profili che utilizza le informazioni di NavigationActivation negli eventi pageswap e pagereveal per impostare i valori di view-transition-name sugli elementi che devono partecipare alla transizione della 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 sugli 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 automatica 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 l'attraversamento della cronologia.
Per facilitare questa operazione, utilizza questa funzione di utilità che imposta temporaneamente 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 ora 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);
}
}
});
Attendi il caricamento dei contenuti con il blocco del rendering
Browser Support
In alcuni casi, potresti voler ritardare il primo rendering di una pagina finché un determinato elemento non è presente nel nuovo DOM. In questo modo si evita lo sfarfallio e si garantisce la stabilità dello stato a cui stai applicando l'animazione.
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 devono essere caricati. Ad esempio, con le immagini, la semplice presenza del tag <img> con il id specificato nell'albero DOM è sufficiente per valutare la condizione come vera. L'immagine potrebbe essere ancora in fase di caricamento.
Prima di bloccare completamente il 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 Segnali web essenziali.
Visualizzare i tipi di transizione nelle transizioni di visualizzazione tra documenti
Le transizioni di visualizzazione tra documenti supportano anche i tipi di transizione di visualizzazione per personalizzare le animazioni e gli elementi acquisiti.
Ad esempio, quando vai alla pagina successiva o precedente in una paginazione, potresti voler utilizzare animazioni diverse a seconda che tu stia andando a una pagina superiore o inferiore della sequenza.
Per impostare questi tipi in anticipo, aggiungili nella regola @@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, utilizza il :active-view-transition-type()selettore di pseudo-classe nello stesso modo delle transizioni di visualizzazione nello 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 puliti 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 navigando.
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
Il feedback degli sviluppatori è sempre apprezzato. Per condividere, segnala un problema al CSS Working Group su GitHub con suggerimenti e domande. Aggiungi il prefisso [css-view-transitions] al problema.
Se riscontri un bug, segnala un bug di Chromium.