Cum se construiește o experiență de derulare infinită cu API-ul Web History

În acest tutorial vom consolida abilitățile noastre API Istoric Web. Vom construi un model UX pe Web, care este iubit și împotrivit în aceeași măsură: scrolling infinit.

Scrolarea infinită este un model de interfață care încarcă conținut nou pe măsură ce ajungem la sfârșitul unei pagini Web date. Scrolarea infinită poate păstra, fără îndoială, angajamentul utilizatorilor atunci când este pus în aplicare cu grijă; unele dintre cele mai bune exemple fiind pe platforme sociale precum Facebook, Twitter și Pinterest.

Este demn de remarcat însă că luăm lucrurile într-o notă semnificativă din ceea ce am construit în tutorialul nostru precedent, Aplicații Web Lovely, Transitions with the Web History. În acest tutorial vom discuta despre interacțiunea derulantă a utilizatorilor, care se poate întâmpla cu o rată foarte frecventă. Dacă nu suntem atenți la codul nostru, acesta va afecta în mod negativ performanța site-ului nostru. Asigurați-vă că ați citit tutorialele anterioare înainte de a încerca acest lucru, doar pentru a vă oferi o înțelegere corectă a ceea ce facem.

Cu toate acestea, dacă sunteți încântați de ideea unei provocări, fixați-vă centurile de siguranță, pregătiți-vă și să începem!

Construirea site-ului Demo

Site-ul nostru este un blog static. Puteți să-l construiți din HTML simplu sau să utilizați un generator de site-uri statice cum ar fi Jekyll, Middleman sau Hexo. Demo-ul nostru pentru acest tutorial arată după cum urmează:

Alb alb vechi.

Există câteva lucruri legate de structura HTML care necesită atenția dvs..

 
  1. După cum puteți vedea din fragmentul de cod de mai sus, articolul trebuie să fie înfășurat într-un element HTML cu un cod unic. Puteți utiliza a div sau a secțiune element, fără nici o restricție în ceea ce privește denumirea id pentru element. 
  2. De asemenea, cu privire la articolul în sine, va trebui să adăugați o Date-articol-id atribut care conține codul corespunzător id numărul articolului.

Simțiți-vă liber să elaborați în termeni de stiluri ale site-urilor web; făcând-o mai colorată, angajând sau adăugând mai mult conținut.

Încărcați JavaScript

În primul rând, încărcați următoarele biblioteci JavaScript în ordinea următoare pentru fiecare pagină a blogului dvs..

  • jquery.js: biblioteca pe care o vom folosi pentru selectarea elementelor, adăugarea unui nou conținut, adăugarea unei noi clase și efectuarea unor solicitări AJAX.
  • history.js: A polyfill care afișează API-ul istoric nativ al browserelor.

Pluginul nostru personalizat jQuery

În plus față de aceste două, va trebui să încărcăm propriul fișier JavaScript în care putem scrie scenariile pentru a le realiza scrolling infinit. Abordarea pe care o vom lua este să îmbrăcăm JavaScript-ul într-un plugin jQuery, mai degrabă decât să îl scriem drept așa cum am făcut în tutorialele anterioare.

Vom porni pluginul cu jQuery Plugin Boilerplate. Aceasta este asemănătoare cu Bootplate-ul HTML5, deoarece oferă o colecție de șabloane, modele și cele mai bune practici pe care se construiește un plugin jQuery.

Descărcați Boilerplate, plasați-o în directorul site-ului dvs. unde locuiesc toate fișierele JavaScript (cum ar fi / active / js /) și redenumiți fișierul la "keepscrolling.jquery.js" (acest nume a fost inspirat de Dory de la Finding Nemo și faimoasa linie "Keep Swimming").

active / js ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 

Pluginul ne va permite să introducem flexibilitate cu Opțiuni sau Setări.

Observarea structurii Pluginului jQuery

Scrierea unui plugin jQuery necesită un mod de gândire ușor diferit, așa că vom examina mai întâi cum este structurat pluginul nostru jQuery înainte de a adăuga orice cod. După cum puteți vedea mai jos, am împărțit codul în patru secțiuni:

; (functie ($, fereastra, document, undefined) "use strict"; // 1. var pluginName = "keepScrolling", default = ; element.exe (, implicit, opțiuni); this._defaults = implicit; this._name = pluginName; this.init (); // 3. $ .extend (Plugin.prototype,  init: function () console.log ("Plugin initialized");); // 4. $ .fn [pluginName] = functie (optiuni) return this.each .data (aceasta, "plugin_" + pluginName)) $ .data (aceasta, "plugin_" + pluginName, Plugin nou (aceasta, opțiuni)));;) (jQuery, fereastră, document); 
  1. În prima secțiune a codului, specificăm numele pluginului nostru, keepScrolling, cu "caz de cămilă" în conformitate cu convențiile comune de denumire JavaScript. Avem de asemenea o variabilă, implicite, care va conține setările implicite pentru plugin.
  2. Apoi, avem funcția principală a pluginului, Conecteaza(). Această funcție poate fi comparată cu un "constructor" care, în acest caz, este de a inițializa plugin-ul și de a îmbina setările implicite cu oricare dintre cele parvenite atunci când instanțializează pluginul.
  3. A treia secțiune este locul în care ne vom compune propriile funcții pentru a servi funcționalitatea de derulare infinită. 
  4. În sfârșit, cea de-a patra secțiune este cea care împachetează întregul lucru într-un plugin jQuery.

Cu toate aceste seturi, acum putem compune JavaScript-ul nostru. Și începem prin definirea opțiunilor implicite ale pluginului nostru.

Opțiunile

; (jQuery, window, document, undefined) "utilizarea strict"; var pluginName = "keepScrolling" document);

După cum puteți vedea mai sus, am stabilit trei opțiuni:

  • podea: un selector id - cum ar fi #podea sau #subsol-pe care o considerăm sfârșitul site-ului sau al conținutului. De obicei, ar fi foaterul site-ului.
  • articol: un selector de clasă care împachetează articolul.
  • date: deoarece nu avem acces la niciun API extern (site-ul nostru este static), trebuie să transmitem o colecție de date despre articol, cum ar fi adresa URL a articolului, ID-ul și titlul în format JSON, deoarece această opțiune.

Funcțiile

Aici avem init (). În această funcție, vom adăuga o serie de funcții care trebuie executate imediat în timpul inițializării site-ului. De exemplu, selectăm etajul site-ului.

$ .extend (Plugin.prototype, // Funcția 'init ()' init: function () this.siteFloor = $ (this.settings.floor); // selectați elementul setat ca etaj al site-ului. ,);

Există, de asemenea, câteva funcții pe care le vom rula in afara inițializarea. Adăugăm aceste funcții pentru a le crea și adăuga după init funcţie.

Primul set de funcții pe care le vom scrie sunt cele pe care le folosim pentru a prelua sau pentru a returna un "lucru"; orice dintr-un String, un Obiect sau un Număr care va fi reutilizabil în celelalte funcții din plugin. Acestea includ:

Obțineți toate articolele de pe pagină:

/ ** * Găsiți și returnează lista articolelor de pe pagină. * @return jQuery Object Lista articolelor selectate. * / getArticles: functie () retur $ (acest element) .find (this.settings.article); ,

Obțineți adresa articolului. În WordPress, acest lucru este popular cunoscut sub numele de "post slug".

/ ** * Returnează articolul Adresa. * @param Integer i Indicele articolului. * @return String Adresa articolului, de ex. 'post-two.html' * / getArticleAddr: funcția (i) var href = window.location.href; var rădăcină = href.substr (0, href.lastIndexOf ("/")); retur rădăcină + "/" + this.settings.data [i] .adresa + ".html"; ,

Obțineți ID-ul și adresa pentru următorul articol pentru a fi preluate.

/ ** * Returnați articolul "următor". * @return Object 'id' și 'url' din următorul articol. * / getNextArticle: function () // Selecteaza ultimul articol. var $ last = this.getArticles () ultim (); var articlePrevURL; / ** * Aceasta este o modalitate simplificată de a determina ID-ul conținutului. * * În acest caz, vom scădea ultimul ID post cu '1'. * În mod ideal, ar trebui să sunăm apelând un punct final al API, de exemplu: * https://www.techinasia.com/wp-json/techinasia/2.0/posts/329951/previous/ * / var articleID = $ last.data ( "articol-id"); articolul varPrevID = parseInt (articolul ID, 10) - 1; // ID-ul anterior // Introduceți datele în opțiune și obțineți adresa de corespondență. pentru (var i = this.settings.data.length - 1; i> = 0; i--) dacă (this.settings.data [i] .id === articolulPrevID) articlePrevURL = this.getArticleAddr (i );  returnează id: articlePrevID, url: articlePrevURL; , 

Următoarele sunt funcțiile utilitare ale pluginului; o funcție care este responsabilă pentru a face un anumit "lucru". Acestea includ:

O funcție care indică dacă un element intră în fereastra de vizualizare. Îl folosim în principal pentru a afirma dacă situl definit "etaj" este vizibil în portul de vizualizare.

/ ** * Detectați dacă elementul țintă este vizibil. * http://stackoverflow.com/q/123999/ * * @return Boolean "adevărat" dacă elementul din fereastra de vizualizare și "false" dacă nu. * / isVisible: funcția () if (instanța țintă a jQuery) target = target [0];  var rect = target.getBoundingClientRect (); retur rect.bottom> 0 && rect.right> 0 && rect.left < ( window.innerWidth || document.documentElement.clientWidth ) && rect.top < ( window.innerHeight || document.documentElement.clientHeight ); , 

O funcție care oprește execuția unei funcții; cunoscut ca debounce. Așa cum am menționat mai devreme, vom avea de-a face cu activitatea de derulare a utilizatorilor, care se va întâmpla la o rată foarte frecventă. Astfel, o funcție în cadrul sul evenimentul va rula frecvent urmând derularea utilizatorului, ceea ce va transforma experiența de derulare pe site lentă sau lentă.

Funcția de debupare de mai sus va reduce frecvența de execuție. Va aștepta timpul specificat, prin aștepta , după ce utilizatorul nu mai derulează înainte de a executa funcția.

/ ** * Returnează o funcție care, atâta timp cât continuă să fie invocată, nu va fi declanșată b *. * Funcția va fi apelată după ce nu mai este chemată N milisecunde. * Dacă trece imediat, declanșați funcția de pe marginea de vârf, în loc de * trailing. * * @link https://davidwalsh.name/function-debounce * @link http://underscorejs.org/docs/underscore.html#section-83 * * @param Function func Funcție pentru debounce * @param  Integer wait Timpul în ms înainte de funcția Run * @param Boolean imediat * @return Void * / esteDebounced: funcția (func, wait, instant) var timeout; funcția return () var context = aceasta, args = argumente; var mai târziu = funcția () timeout = null; dacă (! imediat) func.apply (context, args); ; var callNow = instant &&! timeout; clearTimeout (timeout); timeout = setTimeout (mai târziu, așteptați); dacă (callNow) func.apply (context, args); ; , 

O funcție care determină dacă se procedează sau se întrerupe o operație.

/ ** * Dacă se procedează (sau nu) la preluarea unui articol nou. * @return Boolean [descriere] * / isProceed: function () if (articleFetching // verifica dacă în prezent primim un nou conținut.) || articleEnding // verifică dacă nu mai există un articol pentru încărcare. isVisible (this.siteFloor) // verificați dacă etajul definit este vizibil.) return;  dacă (this.getNextArticle (). id <= 0 )  articleEnding = true; return;  return true; , 

Vom folosi funcția de utilitate precedentă, isProceed (), pentru a examina dacă sunt îndeplinite toate condițiile pentru a continua tragerea conținutului nou. Dacă da, funcția care urmează va fi rulată, preluând noul conținut și adăugându-l după ultimul articol.

/ ** * Funcție pentru preluarea și adăugarea unui articol nou. * @return Void * / fetch: function () // Va proceda sau nu? dacă (! this.isProceed ()) retur;  var principal = acest element; var $ articleLast = this.getArticles () ultima (); $ .ajax (url: this.getNextArticle (). url, tastați: "GET", dataType: "html", înainteSend: function () articleFetching = true / cu succes * prelucrează conținutul, adăugăm conținutul. * / .done (funcția (res) $ articleLast .after (funcția () if (! res) return; return $ (res) .find ("#" + main.id) )) / ** * Când funcția este completă, fie că eșuează, fie că este terminată, * setați întotdeauna "articleFetching" la "false". * Specifică faptul că am terminat descărcarea conținutului nou. * /. întotdeauna (funcția () articleFetching = false;); , 

Adăugați această funcție în cadrul funcției init. Deci, funcția va funcționa de îndată ce plugin-ul este inițializat și apoi va prelua noul conținut atunci când condițiile sunt îndeplinite.

init: funcția () this.siteFloor = $ (this.settings.floor); // selectați elementul setat ca etaj al site-ului. this.fetch (); , 

Apoi, vom adăuga o funcție pentru a modifica istoricul browserului cu ajutorul API-ului Web History. Această funcție particulară este mai complexă decât funcțiile noastre anterioare. Partea dificilă este atunci când trebuie să schimbăm istoricul în timpul derulării utilizatorului, titlul documentului, precum și adresa URL. Următoarea este o ilustrare care vă ajută să simplificați ideea din spatele funcției:

După cum puteți vedea din figură, avem trei linii: "linia de acoperiș", "linia mediană" și "linia podelei" care ilustrează poziția articolului în cadrul ferestrei de vizualizare. Imaginea arată că partea de jos a primului articol, precum și partea de sus a celui de-al doilea articol, se află acum la mijlocul liniei. Nu specifică intenția utilizatorului în privința articolului la care se referă; este primul post sau este al doilea post? Prin urmare, nu vom schimba istoricul browserului când două articole se află în această poziție.

Vom înregistra istoricul la postul următor, când articolul de sus ajunge la "linia de acoperiș", deoarece este nevoie de cea mai mare parte a părții vizibile a ferestrei de vedere.

Înregistrăm istoricul postului anterior, când fundul său atinge "linia de podea", în mod similar, așa cum face acum cea mai mare parte a părții vizibile a ferestrei de vizualizare.

Acesta este codul "în timp" pe care va trebui să îl adăugați:

init: funcția () this.roofLine = Math.ceil (window.innerHeight * 0.4); // setați linia de acoperiș; this.siteFloor = $ (această setare.floor); this.fetch (); , / ** * Modificați istoricul browserului. * @return Void * / history: funcția () if (! window.History.enabled) return;  this.getArticles () .each (funcție (index, articol) var scrollTop = $ (fereastră) .scrollTop (); var articleOffset = Math.floor (article.offsetTop - scrollTop); if (articleOffset> return; var articolFloor = (articolul.clientHeight - (this.threshold * 1.4)); articolulFloor = Math.floor (articolulFloor * -1); dacă (articleOffset < articleFloor )  return;  var articleID = $( article ).data( "article-id" ); articleID = parseInt( articleID, 10 ); var articleIndex; for ( var i = this.settings.data.length - 1; i >= 0; i)) if (this.settings.data [i] .id === articolul ID) articleIndex = i;  var articleURL = this.getArticleAddr (articolulIndex); dacă (window.location.href! == articleURL) var articleTitle = această setare.data [articleIndex] .title; fereastră.Historie.pushState (null, articleTitle, articleURL);  .bind (acest lucru)); , 

În cele din urmă, vom crea o funcție care va rula fetch () si istorie() când utilizatorul derulează pagina. Pentru a face acest lucru, noi creăm o nouă funcție numită scroller (), și rulați-o pe inițializarea pluginului.

/ ** * Funcții pentru a rula în timpul derulării. * @return Void * / scroller: function () window.addEventListener ("scroll", this.isDebounced () (this  ), fals );  

Și după cum puteți vedea mai sus, noi debounce acestea, atât în ​​ceea ce privește performanța AJAX, cât și schimbarea istoricului browserului, sunt o operațiune costisitoare.

Adăugarea unui înlocuitor de conținut

Acest lucru este opțional, dar este recomandat pentru a respecta experiența utilizatorului. Substituentul oferă feedback utilizatorului, semnalând că un nou articol este pe drum.

Mai întâi, vom crea șablonul substituent. În mod obișnuit, acest tip de șablon este pus după subsolul site-ului.

 

Rețineți că articolul care conține numele, structura sa, ar trebui să semene cu conținutul real al blogului dvs. Ajustați structura HTML în consecință.

Stilurile de substituent sunt mai simple. Ea cuprinde toate stilurile de bază pentru ao expune ca articolul actual, animația @keyframe care simulează sensul de încărcare și stilul de comutare a vizibilității (substituentul este inițial ascuns, este afișat numai când elementul părinte are preluam clasă).

.placeholder culoare: @ gri-lumină; padding-top: 60px; padding-bottom: 60px; frontieră-top: 6px solid @ alb-gri; afișare: niciunul; .fetching & display: block;  p display: bloc; înălțime: 20px; fundal: @ gri-light;  & __ header animație-întârziere: .1s; h1 height: 30px; fundal-culoare: @ gri-light;  & __ p-1 animație-întârziere: .2s; lățime: 80%;  & __ p-2 animație-întârziere: .3s; lățime: 70%;  

Apoi actualizăm câteva rânduri pentru a afișa substituentul în timpul solicitării AJAX, după cum urmează.

/ ** * Initializeaza. * @return Void * / init: funcția () this.roofLine = Math.ceil (window.innerHeight * 0.4); this.siteFloor = $ (această setare.floor); this.addPlaceholder (); this.fetch (); this.scroller (); , / ** * Adăugați addPlaceholder. * Dispozitivul de substituire este utilizat pentru a indica încărcarea unui post nou. * @return Void * / addPlaceholder: funcție () var tmplPlaceholder = document.getElementById ("tmpl-placeholder"); tmplPlaceholder = tmplPlaceholder.innerHTML; $ (acest element) .append (tmplPlaceholder); , / ** * Funcție pentru preluarea și adăugarea unui articol nou. * @return Void * / fetch: function () ... // Selectați elementul care înfășoară articolul. var principal = acest element; $ (principală) .addClass (funcția () retur "preluare";);) ... întotdeauna (funcția () ... // Eliminați clasa "preluare". $ (Main) .removeClass (funcția () return "retur";););

Așa ne ocupăm de substituent! Plugin-ul nostru este complet și este momentul să implementăm pluginul.

Implementare

Implementarea plugin-ului este destul de simplă. Desemnăm elementul care înfășoară articolul nostru de pe blog și sună pluginul nostru cu opțiunile setate, după cum urmează.

$ (document) .ready (functie () $ ("#main") .keepScrolling (etaj: "#footer", articol: ".article", date: [id " "post-one", "title": "Post One", "id": 2, "adresa": "post-trei", "titlu": "post trei", "id": 4, ": 5," adresa ":" post-cinci "," titlu ":" Post Five "]);); 

Scroll-ul infinit ar trebui să funcționeze acum.

Caveat: butonul din spate

În acest tutorial, am construit o experiență infinită de parcurgere; ceva pe care l-am văzut de obicei pe site-urile de știri precum Quartz, TechInAsia și în multe aplicații mobile.

Deși se dovedește a fi o modalitate eficientă de a păstra angajamentul utilizatorului, acesta are și un dezavantaj: rupe butonul "Înapoi" în browser. Când faceți clic pe buton, nu vă întoarceți întotdeauna cu întârziere la conținutul sau pagina vizitate anterior.

Site-urile web abordează această problemă în diverse moduri; Cuarț, de exemplu, vă va redirecționa către menționate URL-ul; adresa URL pe care ați vizitat-o ​​anterior, dar nu cea înregistrată prin API-ul Web History. TechInAsia vă va duce pur și simplu înapoi la pagina de pornire.

Înfășurarea în sus

Acest tutorial este lung, acoperind multe lucruri! Unele dintre ele sunt ușor de înțeles, în timp ce unele piese ar putea să nu fie atât de ușor de digerat. Pentru a vă ajuta, am realizat o listă de referințe ca supliment la acest articol.

  • Manipularea istoricului browserului
  • Pagini frumoase, cu o pagină netedă, cu ajutorul API-ului Web History
  • Poate cineva să explice dezvăluirea? funcția în Javascript
  • AJAX pentru designeri frontali
  • dezvoltarea jQuery Plugin: cele mai bune practici

În cele din urmă, căutați în codul sursă complet și vizualizați demo-ul!