Så här skapar du en oändlig rullupplevelse med historia Web API

I denna handledning kommer vi att förstärka våra History Web API-färdigheter. Vi ska bygga ett UX-mönster på webben som är älskat och avskyvärt i lika stor utsträckning: oändlig Scroll.

Oändlig rullning är ett gränssnittsmönster som laddar nytt innehåll när vi når slutet på en viss webbsida. Oändlig rullning kan utan tvekan behålla användarnas engagemang när de genomförs eftertänksamt. några av de bästa exemplen är på sociala plattformar som Facebook, Twitter och Pinterest.

Det är dock värt att notera att vi tar saker och ting upp en väsentlig aning från vad vi byggde i vår tidigare handledning, Lovely, Smooth Page Transitions With the History Web API. I denna handledning behandlar vi användarnas rullningsinteraktion, vilket kan hända i en mycket frekvent takt. Om vi ​​inte är försiktiga med vår kod kommer det i sin tur att påverka vår webbplats prestanda skadligt. Se till att du läser tidigare handledning innan du försöker den här, bara för att ge dig en ordentlig förståelse av vad vi gör.

Ändå, om du är stolt över tanken på en utmaning, fäst din säkerhetsbälte, bli förberedd och låt oss komma igång!

Bygga Demo Website

Vår sida är en statisk blogg. Du kan bygga den från vanlig HTML, eller utnyttja en statisk webbplatsgenerator som Jekyll, Middleman eller Hexo. Vår demo för denna handledning ser ut som följer:

Vanlig gammal vit.

Det finns ett par saker med avseende på HTML-strukturen som kräver din uppmärksamhet.

 
  1. Som du kan se från ovanstående kodbit, bör artikeln vara inlindad i ett HTML-element med ett unikt ID. Du får använda en div eller a sektion element, utan hinder i termen för namnet id för elementet. 
  2. Även på själva artikeln måste du lägga till en uppgifter artikel-id attribut som innehåller motsvarande id Artikelnummeret.

Känn dig fri att utarbeta i termen av webbplatsens stilar; gör det mer färgstarkt, engagerande eller lägga till mer innehåll.

Ladda JavaScript

Till att börja med ladda följande JavaScript-bibliotek i följande ordning till varje sida i din blogg.

  • jquery.js: biblioteket som vi ska använda för att välja element, lägga till nytt innehåll, lägga till ny klass och utföra AJAX-förfrågningar.
  • history.js: a polyfill som shims webbläsarens ursprungliga historia API.

Vårt anpassade jQuery-plugin

Förutom dessa två måste vi ladda vår egen JavaScript-fil där vi kan skriva skript som ska utföras oändlig Scroll. Tillvägagångssättet vi tar är att paketera vår JavaScript i ett jQuery-plugin, istället för att skriva det rakt som vi har gjort i tidigare tutorials.

Vi startar pluginprogrammet med jQuery Plugin Boilerplate. Detta är relaterat till HTML5 Boilerplate genom att det ger en samling mallar, mönster och bästa praxis för att bygga ett jQuery-plugin.

Ladda ner Boilerplate, placera den i katalogen på din webbplats där alla JavaScript-filer finns (t.ex. / tillgångar / js /) och byta namn på filen till "keepscrolling.jquery.js" (det här namnet inspirerades av Dory från Finding Nemo och hennes berömda linje "Keep Swimming").

tillgångar / js ├── keepscrolling.jquery.js ├── keepscrolling.jquery.js.map └── src └── keepscrolling.jquery.js 

Pluggen tillåter oss att introducera flexibilitet med alternativ eller inställningar.

Observation av jQuery Plugin Structure

Att skriva ett jQuery-plugin kräver ett lite annorlunda sätt att tänka, så vi ska först undersöka hur vårt jQuery-plugin är strukturerat innan vi lägger till någon kod. Som du kan se nedan har jag delat koden i fyra avsnitt:

; (funktion ($, fönster, dokument, odefinierad) "använd sträng"; // 1. var pluginName = "keepScrolling", standardvärden = ; // 2. funktionen Plugin (element, alternativ) this.element = element; this.settings = $ .extend (, standardvärden, alternativ); this._defaults = default; this._name = pluginName; this.init (); // 3. $ .extend (Plugin.prototype,  init: function () console.log ("plugin initierad");;; // 4. $ .fn [pluginName] = funktion (alternativ) return this.each (function ) if (! $ .data (detta, "plugin_" + pluginName)) $ .data (detta, "plugin_" + pluginName, nytt plugin (detta alternativ););;) (jQuery, fönster, dokument); 
  1. I den första delen av koden anger vi vårt plugin namn, Fortsätt skrolla, med "camel case" enligt javascript gemensamma namngivningskonventioner. Vi har också en variabel, defaults, som kommer att innehålla standardinställningarna för plugin.
  2. Därefter har vi pluginens huvudfunktion, Plugin (). Denna funktion kan jämföras med en "konstruktör" som i detta fall är att initiera pluginet och slå samman standardinställningarna med vilken som helst som passerat när instansen.
  3. Det tredje avsnittet är där vi ska komponera våra egna funktioner för att betjäna oändlig rullande funktionalitet. 
  4. Slutligen är den fjärde sektionen en som omsluter hela saken till ett jQuery-plugin.

Med alla dessa uppsättningar kan vi nu komponera vår JavaScript. Och vi börjar med att definiera våra standardinställningar för plugin.

Alternativen

; (funktion ($, fönster, dokument, odefinierad) "använd sträng"; var pluginName = "keepScrolling", standard = golv: null, artikel: null, data: ; ...) (jQuery, fönster, dokument);

Som du kan se ovan har vi ställt in tre alternativ:

  • golv: en idväljare-som #golv eller #footer-som vi betraktar slutet på webbplatsen eller innehållet. Vanligtvis skulle det vara sidfoten.
  • artikel: en klassväljare som sveper artikeln.
  • data: Eftersom vi inte har tillgång till några externa API-er (vår webbplats är statisk) måste vi överföra en samling av artikeldata, t.ex. artikeladressen, id och titel i JSON-format som detta alternativargument.

Funktionerna

Här har vi i det(). I denna funktion lägger vi till ett antal funktioner som måste köras omedelbart under platsinitiering. Till exempel väljer vi webbplatsens golv.

$ .extend (Plugin.prototype, // Funktionen "init ()". init: funktion () this.siteFloor = $ (this.settings.floor); // välj elementet som webbplatsens golv. ,;

Det finns också några funktioner som vi kommer att springa utanför initieringen. Vi lägger till dessa funktioner för att skapa och lägga till dem efter i det fungera.

Den första uppsättningen funktioner vi ska skriva är de som vi använder för att hämta eller returnera en "sak"; allt från en sträng, ett objekt eller ett tal som kan återanvändas genom de övriga funktionerna i plugin. Dessa inkluderar saker att:

Få alla artiklar på sidan:

/ ** * Hitta och returnera lista över artiklar på sidan. * @return jQuery Object Lista över valda artiklar. * / getArticles: function () return $ (this.element) .find (this.settings.article); ,

Hämta artikeladressen. I WordPress är detta populärt kallat "post slug".

/ ** * Returnerar artikeladressen. * @param Integer i Artikelindex. * @return String Artikeladressen, t.ex. 'post-two.html' * / getArticleAddr: funktion (i) var href = window.location.href; var root = href.substr (0, href.lastIndexOf ("/")); returnera root + "/" + this.settings.data [i] .address + ".html"; ,

Hämta nästa artikel-id och adress för att hämta.

/ ** * Återgå till "nästa" artikel. * @return Object 'id' och 'url' i nästa artikel. * / getNextArticle: function () // Välj den senaste artikeln. var $ last = this.getArticles (). last (); varartikelPrevURL; / ** * Detta är ett förenklat sätt att bestämma innehålls-ID. * * Här subtraherar vi det sista post-ID med "1". * Idealiskt bör vi ringa en API-ändpunkt, till exempel: * https://www.techinasia.com/wp-json/techinasia/2.0/posts/329951/previous/ * / var articleID = $ last.data ( "artikel-id"); var articlePrevID = parseInt (artikelID, 10) - 1; // Föregående ID // Gå in i alternativet 'data' och få motsvarande adress. för (var jag = this.settings.data.length - 1; i> = 0; i--) if (this.settings.data [i] .id === articlePrevID) articlePrevURL = this.getArticleAddr );  returnera id: articlePrevID, url: articlePrevURL; , 

Följande är pluginens verktygsfunktioner; en funktion som är ansvarig för att göra en viss "sak". Dessa inkluderar:

En funktion som berättar om ett element kommer in i visningsporten. Vi använder det huvudsakligen för att berätta om den definierade platsen "golv" är synlig inom visningsporten.

/ ** * Upptäck om målelementet är synligt. * http://stackoverflow.com/q/123999/ * * @return Boolean "true" om elementet i viewport och "false" om inte. * / isVisible: function () if (target instanceof jQuery) target = target [0];  var rekt = target.getBoundingClientRect (); returnera rect.bottom> 0 && rect.right> 0 && rect.left < ( window.innerWidth || document.documentElement.clientWidth ) && rect.top < ( window.innerHeight || document.documentElement.clientHeight ); , 

En funktion som stoppar ett funktionsutförande känd som debounce. Som tidigare nämnts kommer vi att ta itu med användarrollningsaktivitet som kommer att hända i mycket frekvent takt. Således en funktion inom skrolla händelsen kommer att köras ofta, efter att användaren bläddrar, vilket gör att rullningsupplevelsen på platsen är trög eller långsam.

Ovanstående debounce-funktionen minskar exekveringsfrekvensen. Det väntar på den angivna tiden, genom vänta parameter, efter att användaren slutat bläddra innan funktionen körs.

/ ** * Returnerar en funktion, som, så länge det fortsätter att åberopas, kommer inte b * utlösas. * Funktionen kommer att ringas efter att den slutar att kallas för N millisekunder. * Om det omedelbart passeras, utlös funktionen på framkanten, i stället för * efterföljande. * * @link https://davidwalsh.name/function-debounce * @link http://underscorejs.org/docs/underscore.html#section-83 * * @param Funktion func Funktion att debounce * @param  Heltal vänta Tiden i ms innan funktionen körs * @param Boolean immediately * @return Void * / isDebounced: funktion (func, vänta, omedelbar) var timeout; returfunktion () var context = this, args = arguments; var senare = funktion () timeout = null; om (! omedelbar) func.apply (context, args); ; var callNow = omedelbar &&! timeout; clearTimeout (timeout); timeout = setTimeout (senare vänta); om (callNow) func.apply (context, args); ; , 

En funktion som bestämmer huruvida man ska fortsätta eller avbryta en operation.

/ ** * Huruvida man ska fortsätta (eller inte) hämta en ny artikel. * @return Boolean [beskrivning] * / isProceed: function () if (articleFetching // kontrollera om vi hämtar ett nytt innehåll. || articleEnding // kolla om ingen artikel ska laddas. ||! this. isVisible (this.siteFloor) // kontrollera om det definierade "golvet" är synligt.) return;  om (this.getNextArticle (). id <= 0 )  articleEnding = true; return;  return true; , 

Vi använder den föregående användarfunktionen, isProceed (), att undersöka om alla villkoren är uppfyllda för att fortsätta att dra ut nytt innehåll. Om så är fallet kommer den funktion som följer att köra, hämtar det nya innehållet och lägger till det efter den sista artikeln.

/ ** * Funktion för att hämta och lägga till en ny artikel. * @return Void * / hämta: funktion () // Ska fortsätta eller inte? om (! this.isProceed ()) return;  var huvud = this.element; var $ articleLast = this.getArticles (). last (); $ .ajax (url: this.getNextArticle (). url, typ: "GET", dataType: "html", föreSänd: funktion () articleFetching = true;) / ** * När förfrågan är klar och det lyckas * hämtar innehållet, vi lägger till innehållet. (/) );) / ** * När funktionen är klar, om den är "misslyckad" eller "klar", ställ alltid "artikelFetching" till fel. * Det anger att vi är redo att hämta det nya innehållet. * / .allways (funktion () articleFetching = false;); , 

Lägg till den här funktionen inom i det. Så kommer funktionen att köras så snart plugininitialiseras och sedan hämta det nya innehållet när villkoren är uppfyllda.

init: funktion () this.siteFloor = $ (this.settings.floor); // välj det element som är inställt på webbplatsen. this.fetch (); , 

Därefter lägger vi till en funktion för att ändra webbläsarens historia med API för History Web. Denna speciella funktion är ganska komplexare än våra föregående funktioner. Den knepiga delen här är när vi ska ändra historiken under användarrollen, dokumenttiteln och webbadressen. Nedan följer en illustration för att förenkla tanken bakom funktionen:

Som du kan se från figuren har vi tre linjer: "taklinje", "mittlinje" och "golvlinje" som illustrerar artikelpositionen inom visningsporten. Bilden visar att botten av den första artikeln, liksom toppen av den andra artikeln, nu ligger i mitten. Det specificerar inte användarens avsikt om vilken artikel de tittar på; är det första inlägget eller är det det andra inlägget? Därför skulle vi inte ändra webbläsarhistoriken när två artiklar är i denna position.

Vi kommer att spela in historien till efterföljande inlägg när artikeln toppen når "taklinjen", eftersom det tar det mesta av den synliga delen av visningsporten.

Vi spelar in historien om föregående inlägg när botten träffar "golvlinjen" på samma sätt som det nu tar det mesta av den synliga delen av visningsporten.

Det här är "while" -koden du måste lägga till i:

init: funktion () this.roofLine = Math.ceil (window.innerHeight * 0.4); // ställa in taket; this.siteFloor = $ (this.settings.floor); this.fetch (); , / ** * Ändra webbläsarens historia. * @return Void * / history: function () if ! window.History.enabled) return;  this.getArticles () .each (funktion (index, artikel) var scrollTop = $ (fönster) .scrollTop (); var articleOffset = Math.floor (article.offsetTop - scrollTop); om (articleOffset> this.threshold) return; var articleFloor = (article.clientHeight - (this.threshold * 1.4)); articleFloor = Math.floor (artikelFloor * -1); om (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; jag--) om (this.settings.data [i] .id === artikelID) articleIndex = i;  var articleURL = this.getArticleAddr (articleIndex); om (window.location.href! == articleURL) var articleTitle = this.settings.data [articleIndex] .title; window.History.pushState (null, artikelTitle, artikelURL);  .bind (detta)); , 

Slutligen skapar vi en funktion som kommer att köra hämta() och den historia() när användaren bläddrar på sidan. För att göra så skapar vi en ny funktion som heter scroller (), och kör det på plugininitialiseringen.

/ ** * Funktioner som ska köras under rullningen. * @return Void * / scroller: funktion () window.addEventListener ("scroll", this.isDebounced (funktion () this.fetch (); this.history ();, 300) .bind ), felaktigt);  

Och som du kan se ovan, vi debounce Dessa som både utföra AJAX och ändra webbläsarens historia är en dyr operation.

Lägga till en innehållsinnehavare

Detta är valfritt, men rekommenderas för att respektera användarupplevelsen. Platshållaren ger feedback till användaren, vilket signaliserar att en ny artikel är på väg.

Först skapar vi platsmallen. Vanligtvis är denna typ av mall placerad efter sidfoten.

 

Tänk på att platshållaren artikeln, dess struktur, ska likna det verkliga innehållet i din blogg. Justera HTML-strukturen i enlighet med detta.

Plattformen är enklare. Den innehåller alla grundläggande stilar för att lägga ut det som själva artikeln, animeringen @keyframe som simulerar belastningsavkänningen och stilen för att växla synligheten (platshållaren är ursprungligen gömd, den visas endast när moderelementet har hämta klass).

.platshållare färg: @ grå-ljus; padding-top: 60px; padding-bottom: 60px; border-top: 6px solid @ grå-lättare; display: none; .fetching & display: block;  p display: block; höjd: 20px; bakgrund: @ grå-ljus;  & __ header animation-delay: .1s; h1 höjd: 30px; bakgrundsfärg: @ grå-ljus;  & __ p-1 animationsfördröjning: .2s; bredd: 80%;  & __ p-2 animationsfördröjning: .3s; bredd: 70%;  

Sedan uppdaterar vi några rader för att visa platshållaren under AJAX-förfrågan, enligt följande.

/ ** * Initiera. * @return Void * / init: funktion () this.roofLine = Math.ceil (window.innerHeight * 0.4); this.siteFloor = $ (this.settings.floor); this.addPlaceholder (); this.fetch (); this.scroller (); , / ** * Lägg till addPlaceholder. * Platshållare används för att ange att ett nytt inlägg laddas. * @return Void * / addPlaceholder: funktion () var tmplPlaceholder = document.getElementById ("tmpl-placeholder"); tmplPlaceholder = tmplPlaceholder.innerHTML; $ (this.element) .append (tmplPlaceholder); , / ** * Funktion för att hämta och lägga till en ny artikel. * @return Void * / hämta: funktion () ... // Välj elementet som omsluter artikeln. var huvud = this.element; $ .ajax (... beforeSend: function () ... // Lägg till klassen "hämtar" $ (huvud) .addClass (funktion () return "hämtning";);) ... alltid ... // Ta bort klassen "hämtar" $ (huvud) .removeClass (funktion () returnera "hämtar";););

Så hanterar vi platshållaren! Vår plugin är komplett, och det är dags att distribuera plugin.

Spridning

Implementering av plugin är ganska enkelt. Vi betecknar det element som omsluter vår bloggartikel och kallar vårt plugin med inställda alternativ, enligt följande.

$ (dokument) .ready (funktion () $ ("#main") .keepScrolling (golv: "#footer", artikel: ".article", data: ["id": 1, "adress" "post-one", "title": "Post One", "id": 2, "adress" "adress": "post-tre", "titel": "post tre", "id": 4, "adress" ": 5," adress ":" post-fem "," title ":" Post Five "];;); 

Den oändliga rullningen ska nu fungera.

Tillgång: Back-knappen

I denna handledning har vi byggt en oändlig rullupplevelse; något som vi vanligtvis sett på nyheter som Quartz, TechInAsia och i många mobila applikationer.

Även om det visat sig vara ett effektivt sätt att behålla användarengagemanget, har det också en nackdel: det bryter "Back" -knappen i webbläsaren. När du klickar på knappen, rullar den inte alltid noggrant tillbaka till föregående besökta innehåll eller sida.

Webbplatser adresserar detta problem på olika sätt; Quartz, till exempel, omdirigerar dig till avses URL; webbadressen du tidigare har besökt, men inte den som spelats in via API för webbhistorik. TechInAsia kommer helt enkelt ta dig tillbaka till hemsidan.

Avslutar

Denna handledning är lång och täcker många saker! Några av dem är lätta att förstå, medan vissa bitar kanske inte är så lätta att smälta. För att hjälpa till har jag sammanställt en lista över referenser som ett tillägg till denna artikel.

  • Manipulera webbläsarens historia
  • Underbara, smidiga sidövergångar med API för historia
  • Kan någon förklara "debounce" ?? funktion i Javascript
  • AJAX för Front-End Designers
  • jQuery-pluginutveckling: bästa praxis

Slutligen kolla in hela källkoden och se demoen!