Hvordan bygge en uendelig rulleopplevelse med History Web API

I denne opplæringen skal vi styrke vår History Web API ferdigheter. Vi skal bygge et UX-mønster på nettet som er elsket og avskyet i like stor grad: uendelig scrolling.

Uendelig rulling er et grensesnitt mønster som laster nytt innhold når vi når slutten av en gitt nettside. Uendelig rulling kan uten tvil beholde brukerens engasjement når det gjennomføres omtenksomt; Noen av de beste eksemplene er på sosiale plattformer som Facebook, Twitter og Pinterest.

Det er imidlertid verdt å merke seg at vi tar ting opp en betydelig hakk fra det vi bygget i vår tidligere opplæring, Lovely, Smooth Page Transitions Med History Web API. I denne opplæringen behandler vi brukerens rulleinteraksjon, noe som kan skje med en svært hyppig hastighet. Hvis vi ikke er forsiktige med vår kode, vil det i sin tur påvirke vår nettsideytelse skadelig. Pass på at du leser de tidligere opplæringsprogrammene før du prøver dette, bare for å gi deg en ordentlig forståelse av hva vi gjør.

Likevel, hvis du er begeistret med ideen om en utfordring, fest sikkerhetsbelte, bli forberedt, og la oss komme i gang!

Bygg Demo Website

Vår side er en statisk blogg. Du kan bygge den fra vanlig HTML, eller utnytte en statisk nettsted generator som Jekyll, Middleman, eller Hexo. Vår demonstrasjon for denne opplæringen ser ut som følger:

Vanlig gammel hvit.

Det er et par ting med hensyn til HTML-strukturen som krever oppmerksomhet.

 
  1. Som du kan se fra koden ovenfor, bør artikkelen pakkes inn i et HTML-element med en unik ID. Du kan bruke en div eller a seksjon element, uten restriksjon i termen for å navngi id for elementet. 
  2. Også på selve artikkelen må du legge til en data-artikkel-id attributt som inneholder tilsvarende id nummer av artikkelen.

Du er velkommen til å utarbeide i form av nettstedstiler; gjør det mer fargerikt, engasjerende eller legger til mer innhold.

Last inn JavaScript

Først må du laste inn følgende JavaScript-biblioteker i følgende rekkefølge til hver side av bloggen din.

  • jquery.js: biblioteket som vi skal bruke for å velge elementer, legge til nytt innhold, legge til ny klasse og utføre AJAX-forespørsler.
  • history.js: a polyfill som shims den innfødte historie API av nettleserne.

Vår egendefinerte jQuery-plugin

I tillegg til disse to, må vi laste inn vår egen JavaScript-fil der vi kan skrive skriptene som skal utføres uendelig scrolling. Tilnærmingen vi tar, er å pakke inn JavaScript i et jQuery-plugin, i stedet for å skrive det rett som vi har gjort i de tidligere opplæringsprogrammene.

Vi vil starte pluginet med jQuery Plugin Boilerplate. Dette er relatert til HTML5 Boilerplate ved at den gir en samling av maler, mønstre og beste praksis for å bygge en jQuery-plugin.

Last ned Boilerplate, plasser den i katalogen på nettstedet ditt der alle JavaScript-filene ligger (for eksempel / eiendeler / js /) og omdøpe filen til "keepscrolling.jquery.js" (dette navnet ble inspirert av Dory fra Finding Nemo og hennes berømte linje "Keep Swimming").

eiendeler / js ├── keepscrolling.jquery.js ├── keepscrolling.jquery.js.map └── src └── keepscrolling.jquery.js 

Pluggen vil tillate oss å introdusere fleksibilitet med alternativer eller innstillinger.

Observasjon av jQuery Plugin Structure

Skrive et jQuery-plugin krever en litt annen måte å tenke på, så vi vil først undersøke hvordan vår jQuery-plugin er strukturert før vi legger til i hvilken som helst kode. Som du kan se nedenfor, har jeg delt koden i fire seksjoner:

; (funksjon ($, vindu, dokument, undefined) "bruk strenge"; // 1. var pluginName = "keepScrolling", standardinnstillinger = ; // 2. funksjon Plugin (element, alternativer) this.element = element; this.settings = $ .extend (, standardinnstillinger, alternativer); this._defaults = standardinnstillinger; this._name = pluginName; this.init (); // 3. $ .extend (Plugin.prototype,  init: funksjon () console.log ("Plugin initialized");; // 4. $ .fn [pluginName] = funksjon (alternativer) return this.each (function () if (! $ .data (dette, "plugin_" + pluginName)) $ .data (dette, "plugin_" + pluginName, nytt plugin (dette alternativene););;) (jQuery, vindu, dokument); 
  1. I den første delen av koden spesifiserer vi vårt pluginnavn, keepScrolling, med "camel case" i henhold til JavaScript-felles navngivningskonvensjoner. Vi har også en variabel, mislighold, som vil inneholde standardinnstillingene for plugin.
  2. Deretter har vi pluginens hovedfunksjon, Plugg inn(). Denne funksjonen kan sammenlignes med en "konstruktør" som i dette tilfellet er å initialisere plugin-modulen og slå sammen standardinnstillingene med hvilken som helst bestått når instantiating plugin.
  3. Den tredje delen er hvor vi skal komponere våre egne funksjoner for å betjene uendelig rullingsfunksjonalitet. 
  4. Til slutt er den fjerde delen en som bryter hele greia inn i et jQuery-plugin.

Med alle disse settene kan vi nå komponere JavaScript. Og vi starter med å definere standardinnstillingene for plugin-modulen.

Alternativene

; (funksjon ($, vindu, dokument, udefinert) "bruk strenge"; var pluginName = "keepScrolling", standard = gulv: null, artikkel: null, data: ; ...) (jQuery, dokument);

Som du kan se over, har vi satt tre alternativer lagt ut:

  • gulv: en idvelger-for eksempel #gulv eller #footer-som vi vurderer slutten av nettstedet eller innholdet. Vanligvis ville det være sidebeteksten.
  • artikkel: en klasseväljare som bryter artikkelen.
  • data: siden vi ikke har tilgang til eksterne APIer (vår nettside er statisk), må vi passere en samling av artikkeldata som artikkelen URL, ID og tittel i JSON-format som dette alternativargumentet.

Funksjonene

Her har vi i det(). I denne funksjonen legger vi til en rekke funksjoner som må kjøres umiddelbart under oppstart av nettstedet. For eksempel velger vi nettstedets gulv.

$ .extend (Plugin.prototype, // 'init ()' -funksjonen. init: funksjon () this.siteFloor = $ (this.settings.floor); // velg elementet som nettstedets gulv. ,);

Det er også noen få funksjoner som vi skal kjøre utenfor initialiseringen. Vi legger til disse funksjonene for å lage og legge til dem etter i det funksjon.

Det første settet med funksjoner vi skal skrive er de vi bruker til å hente eller returnere en "ting"; alt fra en streng, et objekt eller et tall som kan gjenbrukes gjennom de andre funksjonene i plugin. Disse inkluderer ting å:

Få alle artikler på siden:

/ ** * Finn og returner liste over artikler på siden. * @return jQuery Object Liste over utvalgte artikler. * / getArticles: function () return $ (this.element) .find (this.settings.article); ,

Få artikkelen adressen. I WordPress er dette populært kjent som "post slug".

/ ** * Returnerer artikkelen Adresse. * @param Integer i Artikkelindeksen. * @return String Artikkeladressen, f.eks. 'post-two.html' * / getArticleAddr: funksjon (i) var href = window.location.href; var root = href.substr (0, href.lastIndexOf ("/")); return root + "/" + this.settings.data [i] .address + ".html"; ,

Få neste artikkel ID og adresse for å hente.

/ ** * Gå tilbake til "neste" artikkel. * @return Object 'ID' og 'url' i neste artikkel. * / getNextArticle: function () // Velg siste artikkel. var $ last = this.getArticles (). last (); var artikkelPrevURL; / ** * Dette er en forenklet måte å bestemme innholds-IDen på. * * Her trekker vi den siste post-IDen med '1'. * Ideelt sett bør vi ringe et API-endepunkt, for eksempel: * https://www.techinasia.com/wp-json/techinasia/2.0/posts/329951/previous/ * / var articleID = $ last.data ( "artikkel-id"); var articlePrevID = parseInt (artikkelID, 10) - 1; // Forrige ID // Gå inn i alternativet 'data', og få den korresponderende adressen. for (var i = this.settings.data.length - 1; i> = 0; i--) if (this.settings.data [i] .id === articlePrevID) articlePrevURL = this.getArticleAddr );  returnere id: articlePrevID, url: articlePrevURL; , 

Følgende er verktøyfunksjonene til pluginet; en funksjon som er ansvarlig for å gjøre en bestemt "ting". Disse inkluderer:

En funksjon som forteller om et element går inn i visningsporten. Vi bruker det hovedsakelig for å fortelle om det definerte nettstedet "gulvet" er synlig i visningsporten.

/ ** * Oppdag om målelementet er synlig. * http://stackoverflow.com/q/123999/ * * @return Boolean 'true' hvis elementet i viewport, og 'false' hvis ikke. * / isVisible: function () if (target instanceof jQuery) target = target [0];  var rekt = target.getBoundingClientRect (); returner rect.bottom> 0 && rect.right> 0 && rect.left < ( window.innerWidth || document.documentElement.clientWidth ) && rect.top < ( window.innerHeight || document.documentElement.clientHeight ); , 

En funksjon som stopper en funksjonsutførelse; kjent som debounce. Som nevnt tidligere, vil vi arbeide med brukerrulningsaktivitet som vil skje i svært hyppig takt. Dermed en funksjon innenfor bla hendelsen vil løpe ofte, etter at brukeren ruller, som vil slå rulleropplevelsen på siden trist eller laggy.

Ovennevnte debounce-funksjonen vil redusere eksekveringsfrekvensen. Det vil vente på den angitte tiden, gjennom vente parameter, etter at brukeren slutter å rulle før du kjører funksjonen.

/ ** * Returnerer en funksjon, som, så lenge den fortsetter å bli påkalt, vil ikke b * utløses. * Funksjonen vil bli kalt etter at den slutter å bli kalt N millisekunder. * Hvis omgående er passert, utløs funksjonen på forkanten, i stedet for * etterfølgende. * * @link https://davidwalsh.name/function-debounce * @link http://underscorejs.org/docs/underscore.html#section-83 * * @param Funksjon func Funksjon for å debounce * @param  Helhet vent Tid i ms før funksjonen kjører * @param Boolean umiddelbar * @return Void * / isDebounced: funksjon (func, vent, umiddelbar) var timeout; returneringsfunksjon () var context = this, args = arguments; var senere = funksjon () timeout = null; hvis (! umiddelbar) func.apply (kontekst, args); ; var callNow = umiddelbar &&! timeout; clearTimeout (timeout); timeout = setTimeout (senere, vent); hvis (callNow) func.apply (kontekst, args); ; , 

En funksjon som bestemmer om å fortsette eller avbryte en operasjon.

/ ** * Enten å fortsette (eller ikke) å hente en ny artikkel. * @return Boolean [beskrivelse] * / isProceed: function () if (articleFetching // se om vi for øyeblikket henter et nytt innhold. || articleEnding // sjekk om ikke mer artikkel å laste. ||! this. isVisible (this.siteFloor) // Sjekk om den definerte "gulvet" er synlig.) return;  hvis (this.getNextArticle (). id <= 0 )  articleEnding = true; return;  return true; , 

Vi bruker den forrige verktøyfunksjonen, isProceed (), for å undersøke om alle vilkårene er oppfylt for å fortsette å trekke ut nytt innhold. I så fall vil funksjonen som følger, løpe, hente det nye innholdet og legge til det etter siste artikkel.

/ ** * Funksjon for å hente og legge til en ny artikkel. * @return Void * / hente: funksjon () // Skal fortsette eller ikke? hvis (! this.isProceed ()) return;  var main = this.element; var $ articleLast = this.getArticles (). last (); $ .ajax (url: this.getNextArticle (). url, type: "GET", dataType: "html", beforeSend: function () articleFetching = true;) / ** * Når forespørselen er fullført og det suksess * henter innholdet, legger vi til innholdet. (/) );) / ** * Når funksjonen er fullført, enten den er "mislykket" eller "ferdig", må du alltid sette "artikkelFetching" til falsk. * Det angir at vi er ferdige med å hente det nye innholdet. * / .allways (funksjon () articleFetching = false;); , 

Legg til denne funksjonen i i det. Så vil funksjonen løpe så snart plugin er initialisert, og deretter hente det nye innholdet når vilkårene er oppfylt.

init: funksjon () this.siteFloor = $ (this.settings.floor); // velg elementet sett som nettsted gulvet. this.fetch (); , 

Deretter legger vi til en funksjon for å endre nettleserloggen med History Web API. Denne spesielle funksjonen er ganske komplisert enn våre tidligere funksjoner. Den vanskelige delen her er når vi skal endre historien under brukerrollen, dokumenttittelen, samt URL-adressen. Følgende er en illustrasjon som bidrar til å forenkle ideen bak funksjonen:

Som du kan se fra figuren, har vi tre linjer: "taklinje", "midtlinje" og "gulvlinje" som illustrerer artikkelposisjonen i visningsporten. Bildet viser at bunnen av den første artikkelen, så vel som toppen av den andre artikkelen, nå er midt på linjen. Det spesifiserer ikke brukerens hensikt om hvilken artikkel de ser på; er det det første innlegget eller er det det andre innlegget? Derfor ville vi ikke endre nettleserloggen når to artikler er i denne posisjonen.

Vi vil registrere historien til den påfølgende posten når artikkelen øverst når taket, da det tar mesteparten av den synlige delen av visningsporten.

Vi registrerer historien til det forrige innlegget når bunnen treffer "gulvlinjen" på samme måte som det nå tar det meste av den synlige delen av visningsporten.

Dette er "mens" -koden du må legge til i:

init: funksjon () this.roofLine = Math.ceil (window.innerHeight * 0.4); // sett taket; this.siteFloor = $ (this.settings.floor); this.fetch (); , / ** * Endre nettleserloggen. * @return Void * / history: function () if ! window.History.enabled) return;  this.getArticles () .each (funksjon (indeks, artikkel) var scrollTop = $ (vindu) .scrollTop (); var articleOffset = Math.floor (article.offsetTop - scrollTop); hvis (articleOffset> this.threshold) return; var articleFloor = (article.clientHeight - (this.threshold * 1.4)); articleFloor = Math.floor (articleFloor * -1); hvis (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; jeg--) hvis (this.settings.data [i] .id === artikkelID) articleIndex = i;  var articleURL = this.getArticleAddr (articleIndex); hvis (window.location.href! == articleURL) var articleTitle = this.settings.data [articleIndex] .title; window.History.pushState (null, artikkelTitle, artikkelURL);  .bind (dette)); , 

Til slutt lager vi en funksjon som vil kjøre hente () og historie() når brukeren ruller siden. For å gjøre det, oppretter vi en ny funksjon kalt scroller (), og kjør det på plugininitialiseringen.

/ ** * Funksjoner som skal kjøres under rullebladet. * @return Void * / scroller: funksjon () window.addEventListener ("scroll", this.isDebounced (funksjon () this.fetch (); this.history ();, 300) .bind ), falsk );  

Og som du kan se over, vi debounce disse som begge utfører AJAX og endrer nettleserhistorikken er en kostbar operasjon.

Legge til en innholdsplassholder

Dette er valgfritt, men anbefales for å respektere brukeropplevelsen. Stedholderen gir tilbakemelding til brukeren, og signaliserer at en ny artikkel er på vei.

Først oppretter vi plassholdermalen. Vanligvis er denne typen mal satt etter sidebeteksten.

 

Husk at stedholderartikkelen, dens struktur, skal likne det virkelige innholdet i bloggen din. Juster HTML-strukturen tilsvarende.

Plassholderen stilene er enklere. Den inneholder alle de grunnleggende stilene for å legge det ut som selve artikkelen, animasjonen @keyframe som simulerer lastefølelsen, og stilen for å bytte synligheten (plassholderen er i utgangspunktet skjult, den vises bare når foreldreelementet har henter klasse).

.plassholder farge: @ grålys; polstring: 60px; polstring-bunn: 60px; border-top: 6px solid @ grå-lighter; skjerm: ingen; .fetching & display: block;  p display: block; høyde: 20px; bakgrunn: @ grålys;  & __ header animasjon-forsinkelse: .1s; h1 høyde: 30px; bakgrunnsfarge: @ grålys;  & __ p-1 animasjonsforsinkelse: .2s; bredde: 80%;  & __ p-2 animasjonsforsinkelse: .3s; bredde: 70%;  

Deretter oppdaterer vi noen linjer for å vise stedholderen under AJAX-forespørselen, som følger.

/ ** * Initialiser. * @return Void * / init: funksjon () this.roofLine = Math.ceil (window.innerHeight * 0.4); this.siteFloor = $ (this.settings.floor); this.addPlaceholder (); this.fetch (); this.scroller (); , / ** * Legg til addPlaceholder. * Plassholder er brukt til å indikere at et nytt innlegg blir lastet. * @return Void * / addPlaceholder: funksjon () var tmplPlaceholder = document.getElementById ("tmpl-placeholder"); tmplPlaceholder = tmplPlaceholder.innerHTML; $ (this.element) .append (tmplPlaceholder); , / ** * Funksjon for å hente og legge til en ny artikkel. * @return Void * / hente: funksjon () ... // Velg elementet som pakker inn artikkelen. var main = this.element; $ .ajax (... beforeSend: function () ... // Legg til hentingsklassen. $ (hoved) .addClass (funksjon () retur "henter";);) ... alltid (funksjon ... // Fjern 'henter'-klassen $ (hoved) .removeClass (funksjon () retur "henter";););

Slik håndterer vi stedholderen! Vår plugin er fullført, og det er på tide å distribuere plugin.

Utplassering

Implementering av plugin er ganske grei. Vi betegner elementet som bryter inn vår bloggartikkel, og kaller vårt plugin med opsjonsinnstillingene, som følger.

$ (dokument) .ready (funksjon () $ ("#main") .keepScrolling (gulv: "#footer", artikkel: ".article", data: ["id": 1, "adresse" "post-one", "title": "Post One", "id": 2, "adresse" "adresse": "post-tre", "tittel": "post tre", "id": 4, "adresse" ": 5," adresse ":" post-fem "," tittel ":" Post fem "];;); 

Den uendelige rulle skal nå fungere.

Advarsel: Back-knappen

I denne opplæringen har vi bygget en uendelig rulleopplevelse; noe vi ofte ser på nyhetssider som Quartz, TechInAsia og i mange mobile applikasjoner.

Selv om det har vist seg å være en effektiv måte å beholde brukerengasjement, har den også en ulempe: den bryter "Back" -knappen i nettleseren. Når du klikker på knappen, ruller den ikke alltid tilbake til det forrige besøkte innholdet eller siden.

Nettsteder adresserer dette problemet på forskjellige måter; Kvarts, for eksempel, vil omdirigere deg til referert URL; Nettadressen du tidligere har besøkt, men ikke den som er registrert gjennom API for nettlogg. TechInAsia tar deg rett og slett tilbake til hjemmesiden.

Wrapping Up

Denne opplæringen er lang og dekker mange ting! Noen av dem er enkle å forstå, mens noen stykker kanskje ikke er så lett å fordøye. For å hjelpe, har jeg satt sammen en liste over referanser som et supplement til denne artikkelen.

  • Manipulerer nettleserloggen
  • Nydelige, glatte sidetransisjoner med History Web API
  • Kan noen forklare â € œdebounceâ € ?? fungere i Javascript
  • AJAX for Front-End Designers
  • jQuery Plugin Development: Best Practices

Til slutt, se på full kildekoden og se demoen!