Miglioramento del flusso degli utenti attraverso le transizioni di pagina
Pubblicato: 2022-03-10Le transizioni tra le pagine possono migliorare l'esperienza conservando (o addirittura migliorando) il contesto dell'utente, mantenendone l'attenzione e fornendo continuità visiva e feedback positivo. Allo stesso tempo, le transizioni di pagina possono anche essere esteticamente piacevoli e divertenti e possono rafforzare il branding se fatte bene.

In questo articolo creeremo, passo dopo passo, una transizione tra le pagine. Parleremo anche dei pro e dei contro di questa tecnica e di come spingerla al limite.
Esempi
Molte app mobili fanno buon uso delle transizioni tra le visualizzazioni. Nell'esempio seguente, che segue le linee guida di progettazione dei materiali di Google, vediamo come l'animazione trasmette relazioni gerarchiche e spaziali tra le pagine.
Perché non usiamo lo stesso approccio con i nostri siti web? Perché siamo d'accordo con l'utente che si sente come se venisse teletrasportato ogni volta che la pagina cambia?
Come eseguire la transizione tra le pagine Web
Quadri SPA
Prima di sporcarci le mani, dovrei dire qualcosa sui framework di applicazioni a pagina singola (SPA). Se stai utilizzando un framework SPA (come AngularJS, Backbone.js o Ember), la creazione di transizioni tra le pagine sarà molto più semplice perché tutto il routing è già gestito da JavaScript. Si prega di fare riferimento alla documentazione pertinente per vedere come eseguire la transizione delle pagine utilizzando il framework prescelto, perché probabilmente ci sono alcuni buoni esempi e tutorial.
La strada sbagliata
Il mio primo tentativo di creare una transizione tra le pagine era più o meno così:
document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });
Il concetto è semplice: usa un'animazione quando l'utente lascia la pagina e un'altra animazione quando viene caricata la nuova pagina.
Tuttavia, ho presto scoperto che questa soluzione aveva alcune limitazioni:
- Non sappiamo quanto tempo impiegherà a caricare la pagina successiva, quindi l'animazione potrebbe non sembrare fluida.
- Non possiamo creare transizioni che combinano i contenuti delle pagine precedenti e successive.
In effetti, l'unico modo per ottenere una transizione fluida e regolare è avere il pieno controllo sul processo di cambio pagina e, quindi, non cambiare affatto la pagina . Quindi, dobbiamo cambiare il nostro approccio al problema.
Il modo giusto
Diamo un'occhiata ai passaggi necessari per creare una semplice transizione di dissolvenza incrociata tra le pagine nel modo giusto. Implica qualcosa chiamato pushState
AJAX (o PJAX), che essenzialmente trasformerà il nostro sito Web in una sorta di sito Web a pagina singola.
Questa tecnica non solo consente di ottenere transizioni fluide e piacevoli, ma beneficeremo di altri vantaggi, che tratteremo in dettaglio più avanti in questo articolo.
Impedisci il comportamento di collegamento predefinito
Il primo passaggio consiste nel creare un listener di eventi click
per tutti i collegamenti da utilizzare, impedendo al browser di eseguire il suo comportamento predefinito e personalizzando il modo in cui gestisce i cambiamenti di pagina.
// Note, we are purposely binding our listener on the document object // so that we can intercept any anchors added in future. document.addEventListener('click', function(e) { var el = e.target; // Go up in the nodelist until we find a node with .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });
Questo metodo per aggiungere un listener di eventi a un elemento padre, invece di aggiungerlo a ogni nodo specifico, è chiamato delega di eventi ed è possibile a causa della natura di bubbling degli eventi dell'API DOM HTML.
Recupera la pagina
Ora che abbiamo interrotto il browser quando tenta di modificare la pagina, possiamo recuperare manualmente quella pagina utilizzando l'API Fetch. Diamo un'occhiata alla seguente funzione, che recupera il contenuto HTML di una pagina quando viene fornito il suo URL.
function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }
Per i browser che non supportano l'API Fetch, prendi in considerazione l'aggiunta del polyfill o l'utilizzo del buon vecchio stile XMLHttpRequest
.
Modifica l'URL corrente
HTML5 ha una fantastica API chiamata pushState
, che consente ai siti Web di accedere e modificare la cronologia del browser senza caricare alcuna pagina. Di seguito, lo utilizziamo per modificare l'URL corrente in modo che sia l'URL della pagina successiva. Nota che questa è una modifica del nostro gestore di eventi clic di ancoraggio precedentemente dichiarato.
if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }
Come avrai notato, abbiamo anche aggiunto una chiamata a una funzione denominata changePage
, che analizzeremo in dettaglio a breve. La stessa funzione verrà chiamata anche nell'evento popstate
, che viene attivato quando la voce della cronologia attiva del browser cambia (come quando un utente fa clic sul pulsante Indietro del proprio browser):
window.addEventListener('popstate', changePage);
Con tutto questo, stiamo fondamentalmente costruendo un sistema di routing molto primitivo, in cui abbiamo modalità attive e passive.
La nostra modalità attiva è in uso quando un utente fa clic su un collegamento e cambiamo l'URL utilizzando pushState
, mentre la modalità passiva è in uso quando l'URL cambia e veniamo avvisati dall'evento popstate
. In entrambi i casi, chiameremo changePage
, che si occupa di leggere il nuovo URL e caricare la pagina pertinente.
Analizza e aggiungi il nuovo contenuto
In genere, le pagine in cui si naviga avranno elementi comuni, come header
e footer
di pagina . Supponiamo di utilizzare la seguente struttura DOM su tutte le nostre pagine (che in realtà è la struttura di Smashing Magazine stesso):
var main = document.querySelector('main'); function changePage() { // Note, the URL has already been changed var url = window.location.href; loadPage(url).then(function(responseText) { var wrapper = document.createElement('div'); wrapper.innerHTML = responseText; var oldContent = document.querySelector('.cc'); var newContent = wrapper.querySelector('.cc'); main.appendChild(newContent); animate(oldContent, newContent); }); }
Animare!
Quando l'utente fa clic su un collegamento, la funzione changePage
recupera l'HTML di quella pagina, quindi estrae il contenitore cc
e lo aggiunge all'elemento main
. A questo punto, abbiamo due contenitori cc
sulla nostra pagina, il primo appartenente alla pagina precedente e il secondo dalla pagina successiva.

La funzione successiva, animate
, si occupa della dissolvenza incrociata dei due contenitori sovrapponendoli, sfumando il vecchio, sfumando nel nuovo e rimuovendo il vecchio contenitore. In questo esempio, sto usando l'API Web Animations per creare l'animazione di dissolvenza, ma ovviamente puoi usare qualsiasi tecnica o libreria che desideri.
function animate(oldContent, newContent) { oldContent.style.position = 'absolute'; var fadeOut = oldContent.animate({ opacity: [1, 0] }, 1000); var fadeIn = newContent.animate({ opacity: [0, 1] }, 1000); fadeIn.onfinish = function() { oldContent.parentNode.removeChild(oldContent); }; }
Il codice finale è disponibile su GitHub.
E queste sono le basi della transizione delle pagine web!
Avvertenze e limitazioni
Il piccolo esempio che abbiamo appena creato è tutt'altro che perfetto. In effetti, non abbiamo ancora tenuto conto di alcune cose:
- Assicurati di influenzare i link corretti.
Prima di modificare il comportamento di un collegamento, dovremmo aggiungere un segno di spunta per assicurarci che debba essere modificato. Ad esempio, dovremmo ignorare tutti i collegamenti contarget="_blank"
(che apre la pagina in una nuova scheda), tutti i collegamenti a domini esterni e alcuni altri casi speciali, comeControl/Command + click
(che apre anche la pagina in una nuova scheda). - Aggiorna gli elementi al di fuori del contenitore di contenuti principale.
Attualmente, quando la pagina cambia, tutti gli elementi al di fuori del contenitorecc
rimangono gli stessi. Tuttavia, alcuni di questi elementi dovrebbero essere modificati (cosa che ora può essere eseguita solo manualmente), incluso iltitle
del documento, l'elemento del menu con la classeactive
e potenzialmente molti altri a seconda del sito Web. - Gestisci il ciclo di vita di JavaScript.
La nostra pagina ora si comporta come una SPA, in cui il browser non cambia pagine stesso. Quindi, dobbiamo occuparci manualmente del ciclo di vita di JavaScript, ad esempio legando e annullando determinati eventi, rivalutando i plug-in e includendo polyfill e codice di terze parti.
Supporto del browser
L'unico requisito per questa modalità di navigazione che stiamo implementando è l'API pushState
, disponibile in tutti i browser moderni. Questa tecnica funziona pienamente come un miglioramento progressivo . Le pagine sono ancora servite e accessibili nel solito modo e il sito Web continuerà a funzionare normalmente quando JavaScript è disabilitato.
Se stai utilizzando un framework SPA, considera invece l'utilizzo della navigazione PJAX, solo per mantenere la navigazione veloce. In tal modo, ottieni supporto legacy e crei un sito Web più SEO-friendly.
Andando ancora oltre
Possiamo continuare a spingere il limite di questa tecnica ottimizzandone alcuni aspetti. I prossimi trucchi velocizzeranno la navigazione, migliorando notevolmente l'esperienza dell'utente.
Utilizzo di una cache
Modificando leggermente la nostra funzione loadPage
, possiamo aggiungere una semplice cache, che assicura che le pagine che sono già state visitate non vengano ricaricate.
var cache = {}; function loadPage(url) { if (cache[url]) { return new Promise(function(resolve) { resolve(cache[url]); }); } return fetch(url, { method: 'GET' }).then(function(response) { cache[url] = response.text(); return cache[url]; }); }
Come avrai intuito, possiamo utilizzare una cache più permanente con l'API Cache o un'altra cache di archiviazione persistente lato client (come IndexedDB).
Animazione della pagina corrente
Il nostro effetto di dissolvenza incrociata richiede che la pagina successiva sia caricata e pronta prima del completamento della transizione. Con un altro effetto, potremmo voler iniziare ad animare la vecchia pagina non appena l'utente fa clic sul collegamento, il che darebbe all'utente un feedback immediato, un ottimo aiuto per le prestazioni percepite.
Usando le promesse, gestire questo tipo di situazione diventa molto facile. Il metodo .all
crea una nuova promessa che viene risolta non appena tutte le promesse incluse come argomenti vengono risolte.
// As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …
Precaricamento della pagina successiva
Utilizzando solo la navigazione PJAX, i cambi di pagina sono generalmente quasi due volte più veloci della navigazione predefinita, perché il browser non deve analizzare e valutare script o stili nella nuova pagina.
Tuttavia, possiamo andare ancora oltre iniziando a precaricare la pagina successiva quando l'utente passa il mouse sopra o inizia a toccare il collegamento.
Come puoi vedere, di solito ci sono da 200 a 300 millisecondi di ritardo nel passaggio del mouse e nel clic dell'utente. Questo è un tempo morto e di solito è sufficiente per caricare la pagina successiva.
Detto questo, precaricare saggiamente perché può facilmente diventare un collo di bottiglia. Ad esempio, se si dispone di un lungo elenco di collegamenti e l'utente lo sta scorrendo, questa tecnica preleverà tutte le pagine perché i collegamenti passano sotto il mouse.
Un altro fattore che potremmo rilevare e prendere in considerazione nel decidere se eseguire il prefetch è la velocità di connessione dell'utente. (Forse ciò sarà possibile in futuro con la Network Information API.)
Uscita parziale
Nella nostra funzione loadPage
, stiamo recuperando l'intero documento HTML, ma in realtà abbiamo solo bisogno del contenitore cc
. Se utilizziamo un linguaggio lato server, possiamo rilevare se la richiesta proviene da una particolare chiamata AJAX personalizzata e, in tal caso, produrre solo il contenitore di cui ha bisogno. Utilizzando l'API Headers, possiamo inviare un'intestazione HTTP personalizzata nella nostra richiesta di recupero.
function loadPage(url) { var myHeaders = new Headers(); myHeaders.append('x-pjax', 'yes'); return fetch(url, { method: 'GET', headers: myHeaders, }).then(function(response) { return response.text(); }); }
Quindi, lato server (usando PHP in questo caso), possiamo rilevare se la nostra intestazione personalizzata esiste prima di emettere solo il contenitore richiesto:
if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }
Ciò ridurrà le dimensioni del messaggio HTTP e ridurrà anche il carico lato server.
Avvolgendo
Dopo aver implementato questa tecnica in un paio di progetti, mi sono reso conto che una libreria riutilizzabile sarebbe stata di grande aiuto. Mi farebbe risparmiare tempo nell'implementarlo in ogni occasione, liberandomi di concentrarmi sugli effetti di transizione stessi.
Così è nata Barba.js, una minuscola libreria (4 KB minimizzati e gZip'd) che astrae tutta questa complessità e fornisce un'API piacevole, pulita e semplice da usare per gli sviluppatori. Tiene conto anche delle visualizzazioni e include transizioni, memorizzazione nella cache, prelettura ed eventi riutilizzabili. È open source e disponibile su GitHub.
Conclusione
Ora abbiamo visto come creare un effetto di dissolvenza incrociata e i pro ei contro dell'utilizzo della navigazione PJAX per trasformare efficacemente il nostro sito Web in una SPA. Oltre al vantaggio della transizione stessa, abbiamo anche visto come implementare semplici meccanismi di memorizzazione nella cache e prelettura per accelerare il caricamento di nuove pagine.
L'intero articolo si basa sulla mia esperienza personale e su ciò che ho imparato dall'implementazione delle transizioni di pagina nei progetti su cui ho lavorato. Se hai domande, non esitare a lasciare un commento o contattami su Twitter: le mie informazioni sono qui sotto!
Ulteriori letture su SmashingMag:
- Transizioni intelligenti nella progettazione dell'esperienza utente
- Progettare nella transizione verso un mondo multi-dispositivo
- Fornire un'esperienza nativa con le tecnologie Web