Come creare un'esperienza di scorrimento infinita con l'API Web di cronologia

In questo tutorial rafforzeremo le nostre abilità di API Web di cronologia. Stiamo costruendo un modello UX sul Web che è amato e detestato in egual misura: scorrimento infinito.

Lo scorrimento infinito è un modello di interfaccia che carica nuovi contenuti quando raggiungiamo la fine di una determinata pagina web. Lo scrolling infinito può probabilmente mantenere il coinvolgimento degli utenti quando implementato in modo ponderato; alcuni dei migliori esempi sono su piattaforme sociali come Facebook, Twitter e Pinterest.

Vale la pena notare, tuttavia, che stiamo prendendo le cose in modo significativo da ciò che abbiamo creato nel precedente tutorial, Lovely, Smooth Page Transitions With the History Web API. In questo tutorial ci occuperemo dell'interazione di scorrimento degli utenti, che può verificarsi a un ritmo molto frequente. Se non stiamo attenti con il nostro codice, a sua volta influenzerà negativamente le prestazioni del nostro sito web. Assicurati di leggere i tutorial precedenti prima di provare questo, solo per darti una corretta comprensione di ciò che stiamo facendo.

Tuttavia, se sei elettrizzato dall'idea di una sfida, allaccia la cintura di sicurezza, preparati e inizia!

Costruire il sito web demo

Il nostro sito è un blog statico. Puoi crearlo da semplice HTML o sfruttare un generatore di siti statico come Jekyll, Middleman o Hexo. La nostra demo per questo tutorial è la seguente:

Semplice vecchio bianco.

Ci sono un paio di cose riguardo alla struttura HTML che richiedono la tua attenzione.

 
  1. Come puoi vedere dallo snippet di codice precedente, l'articolo dovrebbe essere racchiuso all'interno di un elemento HTML con un ID univoco. Puoi usare a div o a sezione elemento, senza restrizioni in termini di denominazione del id per l'elemento. 
  2. Inoltre, sull'articolo stesso, dovrai aggiungere un Dati-ID articolo attributo che contiene il corrispondente id numero dell'articolo.

Sentiti libero di elaborare in termini di stili del sito web; rendendolo più colorato, accattivante o aggiungendo più contenuti.

Carica il codice JavaScript

Per cominciare, carica le seguenti librerie JavaScript nel seguente ordine in ogni pagina del tuo blog.

  • jquery.js: la libreria che useremo per selezionare gli elementi, aggiungere nuovi contenuti, aggiungere nuove classi e eseguire richieste AJAX.
  • history.js: a polyfill che riduce l'API della storia nativa dei browser.

Il nostro plugin jQuery personalizzato

Oltre a questi due, avremo bisogno di caricare il nostro file JavaScript in cui possiamo scrivere gli script da eseguire scorrimento infinito. L'approccio che adotteremo è quello di avvolgere il nostro JavaScript in un plugin jQuery, piuttosto che scriverlo direttamente come abbiamo fatto nelle precedenti esercitazioni.

Inizieremo il plugin con jQuery Plugin Boilerplate. Questo è simile al file HTML5 Boilerplate in quanto fornisce una raccolta di modelli, modelli e best practice su cui costruire un plugin jQuery.

Scarica il Boilerplate, mettilo nella directory del tuo sito Web dove risiedono tutti i file JavaScript (come / attività / js /) e rinominare il file in "keepscrolling.jquery.js" (questo nome è stato ispirato da Dory di Alla ricerca di Nemo e della sua famosa linea "Keep Swimming").

assets / js ├── keepcrolling.jquery.js ├── keepscrolling.jquery.js.map └── src └── keepscrolling.jquery.js 

Il plugin ci permetterà di introdurre la flessibilità con Opzioni o impostazioni.

Osservando la struttura del jQuery Plugin

Scrivere un plugin jQuery richiede un modo di pensare leggermente diverso, quindi esamineremo innanzitutto come il nostro plugin jQuery sia strutturato prima di aggiungere qualsiasi codice. Come puoi vedere di seguito, ho diviso il codice in quattro sezioni:

; (function ($, window, document, undefined) "use strict"; // 1. var pluginName = "keepScrolling", defaults = ; // 2. function Plugin (elemento, opzioni) this.element = elemento; this.settings = $ .extend (, default, opzioni); this._defaults = defaults; this._name = pluginName; this.init (); // 3. $ .extend (Plugin.prototype,  init: function () console.log ("Plugin inizializzato");,); // 4. $ .fn [pluginName] = function (options) return this.each (function () if (! $ .data (questo, "plugin_" + pluginName)) $ .data (questo, "plugin_" + pluginName, nuovo plugin (questa, opzioni)););;) (jQuery, finestra, documento); 
  1. Nella prima sezione del codice, specifichiamo il nostro nome del plugin, continua a scorrere, con "caso cammello" secondo le convenzioni di denominazione comune di JavaScript. Abbiamo anche una variabile, default, che conterrà le impostazioni predefinite del plugin.
  2. Successivamente, abbiamo la funzione principale del plugin, Collegare(). Questa funzione può essere paragonata a un "costruttore" che, in questo caso, deve inizializzare il plugin e unire le impostazioni predefinite con quelle passate durante l'istanziazione del plugin.
  3. La terza sezione è dove comporremo le nostre funzioni per servire la funzionalità di scorrimento infinita. 
  4. Infine, la quarta sezione è quella che avvolge l'intera cosa in un plugin jQuery.

Con tutti questi set, ora possiamo comporre il nostro JavaScript. E iniziamo definendo le opzioni predefinite del nostro plugin.

Le opzioni

; (function ($, window, document, undefined) "use strict"; var pluginName = "keepScrolling", defaults = floor: null, article: null, data: ; ...) (jQuery, finestra, documento);

Come puoi vedere sopra, abbiamo impostato tre opzioni:

  • pavimento: un selettore di ID come #pavimento o #footer-che consideriamo la fine del sito web o il contenuto. In genere, sarebbe il footer del sito.
  • articolo: un selettore di classe che avvolge l'articolo.
  • dati: poiché non abbiamo accesso ad alcuna API esterna (il nostro sito web è statico) dobbiamo passare una raccolta di dati di articoli come l'URL, l'ID e il titolo dell'articolo in formato JSON come argomento di questa opzione.

Le funzioni

Qui abbiamo il dentro(). In questa funzione, aggiungeremo una serie di funzioni che devono essere eseguite immediatamente durante l'inizializzazione del sito. Ad esempio, selezioniamo il piano del sito.

$ .extend (Plugin.prototype, // La funzione 'init ()' init: function () this.siteFloor = $ (this.settings.floor); // seleziona l'insieme di elementi come site floor. ,);

Ci sono anche alcune funzioni che eseguiremo al di fuori l'inizializzazione. Aggiungiamo queste funzioni per creare e aggiungerle dopo il dentro funzione.

La prima serie di funzioni che scriveremo sono quelle che usiamo per recuperare o restituire una "cosa"; qualsiasi cosa, da una stringa, un oggetto o un numero che sarà riutilizzabile in tutte le altre funzioni del plug-in. Questi includono cose a:

Ottieni tutti gli articoli sulla pagina:

/ ** * Trova e restituisce l'elenco degli articoli nella pagina. * @return jQuery Object Elenco di articoli selezionati. * / getArticles: function () return $ (this.element) .find (this.settings.article); ,

Ottieni l'indirizzo dell'articolo. In WordPress, questo è popolarmente noto come "post slug".

/ ** * Restituisce l'indirizzo dell'articolo. * @param intero i L'indice dell'articolo. * @return String L'indirizzo dell'articolo, ad es. 'post-two.html' * / getArticleAddr: function (i) var href = window.location.href; var root = href.substr (0, href.lastIndexOf ("/")); return root + "/" + this.settings.data [i] .address + ".html"; ,

Ottieni l'ID del prossimo articolo e l'indirizzo da recuperare.

/ ** * Restituisce l'articolo "successivo". * @return Object L''id' e 'url' del prossimo articolo. * / getNextArticle: function () // Seleziona l'ultimo articolo. var $ last = this.getArticles (). last (); var articlePrevURL; / ** * Questo è un modo semplificato per determinare l'ID del contenuto. * * Qui, abbiamo sottratto l'ultimo post ID di '1'. * Idealmente, dovremmo chiamare un endpoint API, ad esempio: * https://www.techinasia.com/wp-json/techinasia/2.0/posts/329951/previous/ * / var articleID = $ last.data ( "article-id"); var articlePrevID = parseInt (articleID, 10) - 1; // ID precedente // Passa all'opzione "data" e ottieni l'indirizzo corretto. for (var i = this.settings.data.length - 1; i> = 0; i--) if (this.settings.data [i] .id === articlePrevID) articlePrevURL = this.getArticleAddr (i );  return id: articlePrevID, url: articlePrevURL; , 

Di seguito sono le funzioni di utilità del plugin; una funzione che è responsabile di fare una "cosa" particolare. Questi includono:

Una funzione che indica se un elemento sta entrando nel viewport. Lo usiamo principalmente per dire se il sito definito "floor" è visibile all'interno del viewport.

/ ** * Rileva se l'elemento di destinazione è visibile. * http://stackoverflow.com/q/123999/ * * @return Boolean 'true' se l'elemento in viewport e 'false' in caso contrario. * / isVisible: function () if (target instanceof jQuery) target = target [0];  var rect = target.getBoundingClientRect (); return rect.bottom> 0 && rect.right> 0 && rect.left < ( window.innerWidth || document.documentElement.clientWidth ) && rect.top < ( window.innerHeight || document.documentElement.clientHeight ); , 

Una funzione che interrompe l'esecuzione di una funzione; conosciuto come antirimbalzo. Come accennato in precedenza, avremo a che fare con attività di scorrimento degli utenti che avverranno a un ritmo molto frequente. Quindi una funzione all'interno di scorrere l'evento verrà eseguito frequentemente, seguendo lo scorrimento dell'utente, che trasformerà l'esperienza di scorrimento sul sito lenta o in ritardo.

La funzione di rimbalzo sopra ridurrà la frequenza di esecuzione. Attenderà il tempo specificato, attraverso il aspettare parametro, dopo che l'utente interrompe lo scorrimento prima di eseguire la funzione.

/ ** * Restituisce una funzione, che, finché continua ad essere invocata, non verrà b * attivata. * La funzione verrà chiamata dopo che smetterà di essere chiamata per N millisecondi. * Se viene passato immediatamente, attivare la funzione sul bordo d'attacco, anziché * il trailing. * * @link https://davidwalsh.name/function-debounce * @link http://underscorejs.org/docs/underscore.html#section-83 * * @param Funzione func Funzione di rimbalzo * @param  Intero wait Il tempo in ms prima dell'esecuzione della funzione * @param Boolean immediate * @return Void * / isDebounced: function (func, wait, immediate) var timeout; return function () var context = this, args = arguments; var later = function () timeout = null; if (! immediate) func.apply (context, args); ; var callNow = immediato &&! timeout; clearTimeout (timeout); timeout = setTimeout (dopo, attendere); if (callNow) func.apply (context, args); ; , 

Una funzione che determina se procedere o interrompere un'operazione.

/ ** * Indica se procedere (o meno) al recupero di un nuovo articolo. * @return Boolean [description] * / isProceed: function () if (articleFetching // controlla se stiamo attualmente recuperando un nuovo contenuto. || articleEnding // controlla se non ci sono altri articoli da caricare. ||! this. isVisible (this.siteFloor) // controlla se il "pavimento" definito è visibile.) return;  if (this.getNextArticle () .id <= 0 )  articleEnding = true; return;  return true; , 

Useremo la funzione di utilità precedente, isProceed (), per verificare se tutte le condizioni sono soddisfatte per procedere alla creazione di nuovi contenuti. Se è così, la funzione che segue verrà eseguita, recuperando il nuovo contenuto e aggiungendolo dopo l'ultimo articolo.

/ ** * Funzione per scaricare e aggiungere un nuovo articolo. * @return Void * / fetch: function () // Procede o no? se (! this.isProceed ()) return;  var main = this.element; var $ articleLast = this.getArticles (). last (); $ .ajax (url: this.getNextArticle (). url, digitare: "GET", dataType: "html", beforeSend: function () articleFetching = true;) / ** * Quando la richiesta è completa e con successo * recupera il contenuto, aggiungiamo il contenuto. * / .done (function (res) $ articleLast .after (function () if (! res) return; return $ (res) .find ("#" + main.id) .html (); );) / ** * Una volta completata la funzione, che si tratti di 'fail' o 'done', * impostare sempre 'articleFetching' su false. * Specifica che stiamo facendo il recupero del nuovo contenuto. * / .always (function () articleFetching = false;); , 

Aggiungi questa funzione all'interno del dentro. Quindi la funzione verrà eseguita non appena il plug-in verrà inizializzato e quindi recupererà il nuovo contenuto quando le condizioni saranno soddisfatte.

init: function () this.siteFloor = $ (this.settings.floor); // seleziona l'insieme di elementi come piano del sito. this.fetch (); , 

Successivamente, aggiungeremo una funzione per modificare la cronologia del browser con l'API Web cronologia. Questa particolare funzione è piuttosto più complessa delle nostre funzioni precedenti. La parte difficile qui è quando esattamente dovremmo cambiare la cronologia durante lo scorrimento dell'utente, il titolo del documento, così come l'URL. Di seguito è riportata un'illustrazione per semplificare l'idea alla base della funzione:

Come puoi vedere dalla figura, abbiamo tre linee: "roof-line", "mid-line" e "floor-line" che illustrano la posizione dell'articolo all'interno del viewport. L'immagine mostra che il fondo del primo articolo, così come la parte superiore del secondo articolo, è ora a metà linea. Non specifica l'intenzione dell'utente riguardo a quale articolo stanno guardando; è il primo post o è il secondo post? Pertanto, non cambiere la cronologia del browser quando due articoli sono in questa posizione.

Registreremo la cronologia al post successivo quando l'articolo in alto raggiunge la "linea del tetto", in quanto occupa gran parte della parte visibile del viewport.

Registriamo la cronologia del post precedente quando il suo fondo colpisce la "linea di fondo", allo stesso modo, dato che ora prende la maggior parte della parte visibile del viewport.

Questo è il codice "while" che dovrai aggiungere:

init: function () this.roofLine = Math.ceil (window.innerHeight * 0.4); // imposta la roofLine; this.siteFloor = $ (this.settings.floor); this.fetch (); , / ** * Modifica la cronologia del browser. * @return Void * / history: function () if (! window.History.enabled) return;  this.getArticles () .each (function (index, article) var scrollTop = $ (window) .scrollTop (); var articleOffset = Math.floor (article.offsetTop - scrollTop); if (articleOffset> this.threshold) return; var articleFloor = (article.clientHeight - (this.threshold * 1.4)); articleFloor = Math.floor (articleFloor * -1); if (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 === articleID) articleIndex = i;  var articleURL = this.getArticleAddr (articleIndex); if (window.location.href! == articleURL) var articleTitle = this.settings.data [articleIndex] .title; window.History.pushState (null, articleTitle, articleURL);  .bind (this)); , 

Infine, creiamo una funzione che eseguirà il fetch () e il storia() quando l'utente sta scorrendo la pagina. Per fare ciò creiamo una nuova funzione chiamata scroller (), ed eseguirlo sull'inizializzazione del plugin.

/ ** * Funzioni da eseguire durante lo scorrimento. * @return Void * / scroller: function () window.addEventListener ("scroll", this.isDebounced (function () this.fetch (); this.history ();, 300) .bind (this ), falso);  

E come puoi vedere sopra, noi antirimbalzo questi perché entrambi eseguono AJAX e la modifica della cronologia del browser è un'operazione costosa.

Aggiunta di un segnaposto contenuto

Questo è facoltativo, ma consigliato per rispettare l'esperienza dell'utente. Il segnaposto fornisce un feedback all'utente, segnalando che un nuovo articolo è in arrivo.

Innanzitutto, creiamo il modello segnaposto. Comunemente questo tipo di modello viene inserito dopo il piè di pagina del sito.

 

Tieni presente che l'articolo del segnaposto, la sua struttura, dovrebbe assomigliare al contenuto reale del tuo blog. Adeguare la struttura HTML di conseguenza.

Gli stili segnaposto sono più semplici. Comprende tutti gli stili di base per deporla come l'articolo vero e proprio, l'animazione @keyframe che simula il senso di caricamento e lo stile per attivare la visibilità (il segnaposto è inizialmente nascosto, viene mostrato solo quando l'elemento genitore ha il attraente classe).

.segnaposto color: @ gray-light; padding-top: 60px; imbottitura-fondo: 60px; border-top: 6px solid @ gray-lighter; display: nessuno; .fetching & display: block;  p display: block; altezza: 20px; sfondo: @ grigio-chiaro;  & __ header ritardo di animazione: .1s; h1 height: 30px; background-color: @ gray-light;  & __p-1 ritardo di animazione: .2s; larghezza: 80%;  & __p-2 ritardo di animazione: .3s; larghezza: 70%;  

Quindi aggiorniamo alcune righe per mostrare il segnaposto durante la richiesta AJAX, come segue.

/ ** * Inizializza. * @return Void * / init: function () this.roofLine = Math.ceil (window.innerHeight * 0.4); this.siteFloor = $ (this.settings.floor); this.addPlaceholder (); this.fetch (); this.scroller (); , / ** * Aggiungi l'addPlaceholder. * Il segnaposto viene utilizzato per indicare che un nuovo post è in fase di caricamento. * @return Void * / addPlaceholder: function () var tmplPlaceholder = document.getElementById ("tmpl-placeholder"); tmplPlaceholder = tmplPlaceholder.innerHTML; $ (this.element) .append (tmplPlaceholder); , / ** * Funzione per recuperare e aggiungere un nuovo articolo. * @return Void * / fetch: function () ... // Seleziona l'elemento che avvolge l'articolo. var main = this.element; $ .ajax (... beforeSend: function () ... // Aggiungi la classe 'fetching. $ (main) .addClass (function () return "fetching;;);) ... always (function () ... // Rimuovi la classe 'fetching'. $ (Main) .removeClass (function () return "fetching;););

È così che gestiamo il segnaposto! Il nostro plug-in è completo ed è il momento di implementare il plug-in.

Distribuzione

La distribuzione del plugin è abbastanza semplice. Noi designiamo l'elemento che avvolge il nostro articolo del blog e chiamiamo il nostro plugin con le opzioni impostate, come segue.

$ (document) .ready (function () $ ("#main") .keepScrolling (floor: "#footer", article: ".article", data: ["id": 1, "indirizzo": "post-one", "title": "Post One", "id": 2, "address": "post-two", "title": "Post Two", "id": 3, "address": "post-three", "title": "Post Three", "id": 4, "address": "post-four", "title": "Post Four", "id ": 5," indirizzo ":" post-five "," title ":" Post Five "]);); 

Il rotolo infinito dovrebbe ora funzionare.

Avvertenza: il pulsante Indietro

In questo tutorial, abbiamo costruito un'esperienza di scroll infinita; qualcosa che abbiamo comunemente visto su siti di notizie come Quartz, TechInAsia e in molte applicazioni mobili.

Anche se si è dimostrato un modo efficace per mantenere il coinvolgimento degli utenti, ha anche uno svantaggio: interrompe il pulsante "Indietro" nel browser. Quando si fa clic sul pulsante, non sarà sempre possibile scorrere in modo accurato indietro al contenuto o alla pagina visitati precedenti.

I siti web affrontano questo problema in vari modi; Il quarzo, ad esempio, ti reindirizzerà a di cui URL; l'URL che hai visitato in precedenza, ma non quello registrato tramite l'API della cronologia web. TechInAsia ti riporterà semplicemente alla homepage.

Avvolgendo

Questo tutorial è lungo e copre molte cose! Alcuni di loro sono facili da capire, mentre alcuni pezzi potrebbero non essere così facili da digerire. Per aiutare, ho messo insieme un elenco di riferimenti come supplemento a questo articolo.

  • Manipolazione della cronologia del browser
  • Transizioni di pagina belle e fluide con l'API Web cronologia
  • Qualcuno può spiegare il "Debutto"? funzione in Javascript
  • AJAX per progettisti front-end
  • Sviluppo di plugin jQuery: best practice

Infine, guarda il codice sorgente completo e visualizza la demo!