Îmbunătățirea fluxului de utilizatori prin tranzițiile paginilor

Publicat: 2022-03-10
Rezumat rapid ↬ De fiecare dată când experiența unui utilizator este întreruptă, șansa ca acesta să plece crește. Schimbarea de la o pagină la alta va cauza adesea această întrerupere prin afișarea unui fulger alb fără conținut, prin încărcarea durată prea mult sau prin scoaterea utilizatorului din contextul în care se afla înainte de deschiderea noii pagini.

Tranzițiile între pagini pot îmbunătăți experiența reținând (sau chiar îmbunătățind) contextul utilizatorului, menținându-i atenția și oferind continuitate vizuală și feedback pozitiv. În același timp, tranzițiile de pagină pot fi, de asemenea, plăcute și distractive din punct de vedere estetic și pot întări brandingul atunci când sunt făcute bine.

Page Transitions

În acest articol, vom crea, pas cu pas, o tranziție între pagini. Vom vorbi, de asemenea, despre avantajele și dezavantajele acestei tehnici și despre cum să o împingeți la limită.

Exemple

Multe aplicații mobile folosesc bine tranzițiile între vizualizări. În exemplul de mai jos, care urmează liniile directoare Google de proiectare a materialelor, vedem cum animația transmite relații ierarhice și spațiale între pagini.

De ce nu folosim aceeași abordare cu site-urile noastre web? De ce suntem de acord ca utilizatorul să simtă că este teleportat de fiecare dată când pagina se schimbă?

Mai multe după săritură! Continuați să citiți mai jos ↓

Cum se face tranziția între paginile web

Cadre SPA

Înainte de a ne murdari mâinile, ar trebui să spun ceva despre cadrele de aplicare a unei singure pagini (SPA). Dacă utilizați un cadru SPA (cum ar fi AngularJS, Backbone.js sau Ember), atunci crearea tranzițiilor între pagini va fi mult mai ușoară, deoarece toată rutarea este deja gestionată de JavaScript. Vă rugăm să consultați documentația relevantă pentru a vedea cum să faceți tranziția paginilor utilizând cadrul pe care l-ați ales, deoarece probabil există câteva exemple și tutoriale bune.

Calea gresita

Prima mea încercare de a crea o tranziție între pagini arăta mai mult sau mai puțin așa:

 document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });

Conceptul este simplu: utilizați o animație când utilizatorul părăsește pagina și o altă animație când se încarcă noua pagină.

Cu toate acestea, am constatat curând că această soluție are câteva limitări:

  • Nu știm cât timp va dura pagina următoare să se încarce, așa că animația ar putea să nu arate fluidă.
  • Nu putem crea tranziții care combină conținut din paginile anterioare și următoare.

De fapt, singura modalitate de a realiza o tranziție fluidă și lină este să aveți control deplin asupra procesului de schimbare a paginii și, prin urmare, să nu schimbați pagina deloc . Prin urmare, trebuie să ne schimbăm abordarea asupra problemei.

Calea cea buna

Să ne uităm la pașii implicați în crearea unei tranziții simple încrucișate între pagini în mod corect. Implica ceva numit pushState AJAX (sau PJAX), care va transforma, în esență, site-ul nostru într-un fel de site cu o singură pagină.

Nu numai că această tehnică realizează tranziții fine și plăcute, dar vom beneficia de alte avantaje, pe care le vom acoperi în detaliu mai târziu în acest articol.

Preveniți comportamentul legăturii implicite

Primul pas este să creați un ascultător de evenimente de click pentru toate linkurile de utilizat, împiedicând browserul să-și îndeplinească comportamentul implicit și personalizând modul în care gestionează modificările paginii.

 // 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; } });

Această metodă de adăugare a unui ascultător de evenimente la un element părinte, în loc să-l adauge la fiecare nod specific, se numește delegare de evenimente și este posibilă datorită naturii de bule de evenimente a API-ului HTML DOM.

Preluați pagina

Acum că am întrerupt browserul când încearcă să schimbe pagina, putem prelua manual pagina respectivă folosind API-ul Fetch. Să ne uităm la următoarea funcție, care preia conținutul HTML al unei pagini atunci când i se oferă adresa URL.

 function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }

Pentru browserele care nu acceptă API-ul Fetch, luați în considerare adăugarea polyfill-ului sau folosirea XMLHttpRequest de modă veche.

Schimbați adresa URL curentă

HTML5 are un API fantastic numit pushState , care permite site-urilor web să acceseze și să modifice istoricul browserului fără a încărca nicio pagină. Mai jos, îl folosim pentru a modifica adresa URL curentă, astfel încât să fie adresa URL a paginii următoare. Rețineți că aceasta este o modificare a gestionarului nostru de clic-eveniment de ancorare declarat anterior.

 if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }

După cum probabil ați observat, am adăugat și un apel la o funcție numită changePage , pe care o vom analiza în detaliu în scurt timp. Aceeași funcție va fi apelată și în evenimentul popstate , care este declanșat atunci când intrarea în istoricul activ al browserului se modifică (ca atunci când un utilizator dă clic pe butonul înapoi al browserului său):

 window.addEventListener('popstate', changePage);

Cu toate acestea, practic construim un sistem de rutare foarte primitiv, în care avem moduri active și pasive.

Modul nostru activ este utilizat atunci când un utilizator dă clic pe un link și modificăm adresa URL folosind pushState , în timp ce modul pasiv este utilizat când URL-ul se schimbă și suntem notificați de evenimentul popstate . În ambele cazuri, vom apela changePage , care se ocupă de citirea noului URL și de încărcarea paginii relevante.

Analizați și adăugați conținut nou

De obicei, paginile în care se navighează vor avea elemente comune, cum ar fi header și footer . Să presupunem că folosim următoarea structură DOM pe toate paginile noastre (care este de fapt structura Smashing Magazine în sine):

Anima!

Când utilizatorul face clic pe un link, funcția changePage preia codul HTML al paginii respective, apoi extrage containerul cc și îl adaugă la elementul main . În acest moment, avem două containere cc pe pagina noastră, primul aparținând paginii precedente și al doilea de pe pagina următoare.

Următoarea funcție, animate , are grijă de estomparea încrucișată a celor două containere suprapunându-le, estomparea celui vechi, estomparea în cel nou și îndepărtarea containerului vechi. În acest exemplu, folosesc API-ul Web Animations pentru a crea animația de estompare, dar, desigur, puteți folosi orice tehnică sau bibliotecă doriți.

 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); }; }

Codul final este disponibil pe GitHub.

Și acestea sunt elementele de bază ale tranziției paginilor web!

Avertismente și limitări

Micul exemplu pe care tocmai l-am creat este departe de a fi perfect. De fapt, încă nu am ținut cont de câteva lucruri:

  • Asigurați-vă că afectăm linkurile corecte.
    Înainte de a schimba comportamentul unui link, ar trebui să adăugăm o verificare pentru a ne asigura că trebuie schimbat. De exemplu, ar trebui să ignorăm toate linkurile cu target="_blank" (care deschide pagina într-o filă nouă), toate linkurile către domenii externe și alte cazuri speciale, cum ar fi Control/Command + click (care deschide și pagina în o filă nouă).
  • Actualizați elementele din afara containerului de conținut principal.
    În prezent, când pagina se schimbă, toate elementele din afara containerului cc rămân aceleași. Cu toate acestea, unele dintre aceste elemente ar trebui modificate (ceea ce acum ar putea fi făcut doar manual), inclusiv title documentului, elementul de meniu cu clasa active și, potențial, multe altele în funcție de site-ul web.
  • Gestionați ciclul de viață al JavaScript.
    Pagina noastră se comportă acum ca un SPA, în care browserul nu schimbă paginile în sine. Așadar, trebuie să avem grijă manual de ciclul de viață JavaScript - de exemplu, legarea și dezlegarea anumitor evenimente, reevaluarea pluginurilor și includerea polyfillurilor și a codului terță parte.

Suport pentru browser

Singura cerință pentru acest mod de navigare pe care îl implementăm este API-ul pushState , care este disponibil în toate browserele moderne. Această tehnică funcționează pe deplin ca o îmbunătățire progresivă . Paginile sunt încă difuzate și accesibile în mod obișnuit, iar site-ul web va continua să funcționeze normal atunci când JavaScript este dezactivat.

Dacă utilizați un cadru SPA, luați în considerare utilizarea navigației PJAX, doar pentru a menține navigarea rapidă. Făcând acest lucru, obțineți suport vechi și creați un site web mai prietenos cu SEO.

Mergând și mai departe

Putem continua să depășim limita acestei tehnici prin optimizarea anumitor aspecte ale acesteia. Următoarele trucuri vor accelera navigarea, îmbunătățind semnificativ experiența utilizatorului.

Folosind un cache

Schimbând ușor funcția loadPage , putem adăuga un simplu cache, care se asigură că paginile care au fost deja vizitate nu sunt reîncărcate.

 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]; }); }

După cum probabil ați ghicit, putem folosi un cache mai permanent cu API-ul Cache sau un alt cache de stocare persistentă la nivelul clientului (cum ar fi IndexedDB).

Animarea paginii curente

Efectul nostru de crossfade necesită ca următoarea pagină să fie încărcată și gata înainte de finalizarea tranziției. Cu un alt efect, ar putea dori să începem să animam pagina veche de îndată ce utilizatorul face clic pe link, ceea ce i-ar oferi utilizatorului feedback imediat, un mare ajutor pentru performanța percepută.

Folosind promisiuni, gestionarea acestui tip de situație devine foarte ușoară. Metoda .all creează o nouă promisiune care se rezolvă de îndată ce toate promisiunile incluse ca argumente sunt rezolvate.

 // As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …

Preaducerea paginii următoare

Folosind doar navigarea PJAX, modificările paginii sunt de obicei aproape de două ori mai rapide decât navigarea implicită, deoarece browserul nu trebuie să analizeze și să evalueze niciun script sau stil de pe pagina nouă.

Cu toate acestea, putem merge și mai departe, începând să preîncărcăm pagina următoare când utilizatorul trece cu mouse-ul peste sau începe să atingă linkul.

După cum puteți vedea, există de obicei 200 până la 300 de milisecunde de întârziere în trecerea cu mouse-ul și clicul utilizatorului. Acesta este un timp mort și de obicei este suficient pentru a încărca pagina următoare.

Acestea fiind spuse, preluați cu înțelepciune deoarece poate deveni cu ușurință un blocaj. De exemplu, dacă aveți o listă lungă de link-uri și utilizatorul o derulează, această tehnică va preîncărca toate paginile, deoarece linkurile trec pe sub mouse.

Un alt factor pe care l-am putea detecta și luăm în considerare atunci când decidem dacă să prelevam este viteza de conectare a utilizatorului. (Poate că acest lucru va fi posibil în viitor cu API-ul Network Information.)

Ieșire parțială

În funcția noastră loadPage , preluăm întregul document HTML, dar de fapt avem nevoie doar de containerul cc . Dacă folosim un limbaj pe partea de server, putem detecta dacă cererea provine dintr-un anumit apel AJAX personalizat și, dacă da, putem scoate doar containerul de care are nevoie. Utilizând API-ul Headers, putem trimite un antet HTTP personalizat în cererea noastră de preluare.

 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(); }); }

Apoi, pe partea serverului (folosind PHP în acest caz), putem detecta dacă antetul nostru personalizat există înainte de a scoate numai containerul necesar:

 if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }

Acest lucru va reduce dimensiunea mesajului HTTP și, de asemenea, va reduce încărcarea pe partea serverului.

Încheierea

După implementarea acestei tehnici în câteva proiecte, mi-am dat seama că o bibliotecă reutilizabilă ar fi de mare ajutor. Mi-ar economisi timp în implementarea lui de fiecare dată, eliberându-mă să mă concentrez asupra efectelor de tranziție în sine.

Astfel s-a născut Barba.js, o bibliotecă minusculă (4 KB miniat și gZip'd) care abstrage toată această complexitate și oferă un API frumos, curat și simplu de utilizat pentru dezvoltatori. De asemenea, ține cont de vizualizări și vine cu tranziții reutilizabile, stocare în cache, preîncărcare și evenimente. Este open source și disponibil pe GitHub.

Concluzie

Am văzut acum cum să creăm un efect de crossfade și avantajele și dezavantajele utilizării navigației PJAX pentru a transforma în mod eficient site-ul nostru într-un SPA. Pe lângă beneficiul tranziției în sine, am văzut și cum să implementăm mecanisme simple de stocare în cache și preîncărcare pentru a accelera încărcarea noilor pagini.

Întregul articol se bazează pe experiența mea personală și pe ceea ce am învățat din implementarea tranzițiilor de pagină în proiectele la care am lucrat. Dacă aveți întrebări, nu ezitați să lăsați un comentariu sau să mă contactați pe Twitter - informațiile mele sunt mai jos!

Citiți suplimentare despre SmashingMag:

  • Tranziții inteligente în designul experienței utilizatorului
  • Proiectarea în tranziția către o lume cu mai multe dispozitive
  • Oferirea unei experiențe native cu tehnologii web