In deze zelfstudie gaan we onze API-vaardigheden van de History Web versterken. We gaan een UX-patroon bouwen op het web dat geliefd en gehaat is in gelijke mate: oneindig scrollen.
Oneindig scrollen is een interfacepatroon dat nieuwe inhoud laadt wanneer we het einde van een bepaalde webpagina bereiken. Oneindig scrollen kan aantoonbaar de betrokkenheid van gebruikers behouden wanneer deze zorgvuldig wordt geïmplementeerd; enkele van de beste voorbeelden zijn op sociale platforms zoals Facebook, Twitter en Pinterest.
Het is echter vermeldenswaard dat we een flinke stap verder gaan in wat we hebben gebouwd in onze vorige tutorial, Lovely, Smooth Page Transitions With the History Web API. In deze zelfstudie hebben we te maken met de scroll-interactie van de gebruiker, wat zeer frequent kan gebeuren. Als we niet voorzichtig zijn met onze code, zal dit op zijn beurt de prestaties van onze website nadelig beïnvloeden. Zorg ervoor dat je de vorige tutorials hebt gelezen voordat je deze hebt geprobeerd, alleen om je een goed begrip te geven van wat we doen.
Toch, als je dol bent op het idee van een uitdaging, maak je je veiligheidsgordel vast, bereid je voor en laten we aan de slag gaan!
Onze site is een statische blog. Je kunt het uit gewone HTML bouwen, of gebruikmaken van een statische site-generator zoals Jekyll, Middleman of Hexo. Onze demo voor deze tutorial ziet er als volgt uit:
Gewoon oud wit.Er zijn een paar dingen met betrekking tot de HTML-structuur die uw aandacht vereisen.
- Zoals u kunt zien aan de hand van het bovenstaande codefragment, moet het artikel worden ingepakt in een HTML-element met een unieke ID. U mag een
div
of asectie
element, zonder enige beperking in termen van het benoemen van deID kaart
voor het element.- Op het artikel zelf moet u ook een toevoegen
data-article-id
attribuut dat het overeenkomstige bevatID kaart
nummer van het artikel.Voel je vrij om uit te wijden in termen van de website-stijlen; het meer kleurrijk, boeiend maken of meer inhoud toevoegen.
Laad de JavaScript
Om te beginnen, laadt u de volgende JavaScript-bibliotheken in de volgende volgorde op elke pagina van uw blog.
- jquery.js: de bibliotheek die we zullen gebruiken voor het selecteren van elementen, het toevoegen van nieuwe inhoud, het toevoegen van nieuwe lesgroepen en het uitvoeren van AJAX-verzoeken.
- history.js: een polyfill welke de native history API van de browsers shims.
Onze aangepaste jQuery-plug-in
Naast deze twee moeten we ons eigen JavaScript-bestand laden waarin we de scripts kunnen schrijven die moeten worden uitgevoerd oneindig scrollen. De aanpak die we zullen nemen is om onze JavaScript in een jQuery-plug-in te plaatsen, in plaats van het rechtstreeks te schrijven, zoals we in de vorige tutorials hebben gedaan.
We starten de plug-in met jQuery Plugin Boilerplate. Dit lijkt op de HTML5-boilerscherm omdat het een verzameling sjablonen, patronen en beste werkwijzen biedt waarop een jQuery-plug-in kan worden gebouwd.
Download de Boilerplate, plaats deze in de directory van uw website waar alle JavaScript-bestanden zich bevinden (zoals
/ Assets / js /
) en hernoem het bestand in "keepscrolling.jquery.js" (deze naam is geïnspireerd door Dory van Finding Nemo en haar beroemde regel "Keep Swimming").assets / js ├── keepscrolling.jquery.js ├── keepscrolling.jquery.js.map └── src └── keepscrolling.jquery.jsMet de plug-in kunnen we flexibiliteit introduceren met opties of instellingen.
Het observeren van de jQuery-plug-insstructuur
Het schrijven van een jQuery-plug-in vereist een enigszins andere manier van denken, dus we zullen eerst onderzoeken hoe onze jQuery-plug-in is gestructureerd voordat we een code toevoegen. Zoals je hieronder kunt zien, heb ik de code in vier delen verdeeld:
; (function ($, window, document, undefined) "gebruik strict"; // 1. var pluginName = "keepScrolling", standaardinstellingen = ; // 2. functie Plugin (element, opties) this.element = element; this.settings = $ .extend (, standaardinstellingen, opties); this._defaults = standaardinstellingen; this._name = pluginName; this.init (); // 3. $ .extend (Plugin.prototype, init: function () console.log ("Plugin initialized");,); // 4. $ .fn [pluginName] = function (options) return this.each (function () if (! $ .data (dit, "plugin_" + pluginName)) $ .data (this, "plugin_" + pluginName, new Plugin (this, options)););;) (jQuery, window, document);
- In het eerste gedeelte van de code geven we onze plugin-naam op,
blijf scrollen
, met "camel case" volgens JavaScript-standaard naamgevingsconventies. We hebben ook een variabele,defaults
, die de standaardinstellingen van de plug-in zal bevatten.- Vervolgens hebben we de hoofdfunctie van de plug-in,
Inpluggen()
. Deze functie kan worden vergeleken met een "constructor" die in dit geval is om de plug-in te initialiseren en de standaardinstellingen samen te voegen met een fout die wordt doorgegeven bij het instantiëren van de plug-in.- In het derde gedeelte zullen we onze eigen functies samenstellen om de oneindige scrollfunctionaliteit te bedienen.
- Ten slotte is de vierde sectie een sectie die het hele ding in een jQuery-plug-in verpakt.
Met al deze ingesteld, kunnen we nu onze JavaScript samenstellen. En we beginnen met het definiëren van de standaardopties van onze plug-in.
De opties
; (function ($, window, document, undefined) "gebruik strict"; var pluginName = "keepScrolling", standaard = floor: null, article: null, data: ; ...) (jQuery, window, document );Zoals je hierboven kunt zien, hebben we drie opties vastgelegd:
verdieping
: een id-selector, zoals#verdieping
of#footer
-die we beschouwen als het einde van de website of de inhoud. Meestal is dit de voettekst van de site.artikel
: een klassenkiezer die het artikel omwikkelt.gegevens
: omdat we geen toegang hebben tot externe API's (onze website is statisch), moeten we een verzameling artikelgegevens, zoals de artikel-URL, -ID en -titel in JSON-indeling doorgeven als argument voor deze optie.De functies
Hier hebben we de
in het()
. In deze functie zullen we een aantal functies toevoegen die onmiddellijk moeten worden uitgevoerd tijdens de initialisatie van de site. We selecteren bijvoorbeeld de sitevloer.$ .extend (Plugin.prototype, // De 'init ()' functie. init: function () this.siteFloor = $ (this.settings.floor); // selecteer het element dat is ingesteld als de sitevloer. ,);Er zijn ook een paar functies die we zullen uitvoeren buiten de initialisatie. We voegen deze functies toe om ze te maken en toe te voegen na de
in het
functie.De eerste set functies die we zullen schrijven zijn degene die we gebruiken om een "ding" op te halen of terug te sturen; alles van een String, een Object of een Nummer dat herbruikbaar is in de andere functies van de plug-in. Deze omvatten dingen om:
Krijg alle artikelen op de pagina:
/ ** * Zoek en retourneert een lijst met artikelen op de pagina. * @return jQuery Object Lijst met geselecteerde artikelen. * / getArticles: function () return $ (this.element) .find (this.settings.article); ,Download het artikeladres. In WordPress wordt dit in de volksmond de "postslak" genoemd.
/ ** * Retourneert het artikeladres. * @param Integer i De artikelindex. * @return String Het artikeladres, b.v. '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"; ,Download de volgende id en het adres van het artikel om op te halen.
/ ** * Retourneer het "volgende" artikel. * @return Object De 'id' en 'url' van het volgende artikel. * / getNextArticle: function () // Selecteer het laatste artikel. var $ last = this.getArticles (). last (); var articlePrevURL; / ** * Dit is een vereenvoudigde manier om de inhoud-ID te bepalen. * * Hierin trekken we de laatste post-ID af met '1'. * Idealiter zouden we call een API-eindpunt moeten noemen, bijvoorbeeld: * https://www.techinasia.com/wp-json/techinasia/2.0/posts/329951/previous/ * / var articleID = $ last.data ( "artikel-id"); var articlePrevID = parseInt (articleID, 10) - 1; // Previous ID // Loop in de optie 'data' en verkrijg het overeenstemmende adres. 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; ,Hieronder volgen de hulpprogramma's van de plug-in; een functie die verantwoordelijk is voor het doen van een bepaald "ding". Waaronder:
Een functie die aangeeft of een element de viewport invoert. We gebruiken het voornamelijk om te bepalen of de gedefinieerde site "verdieping" zichtbaar is in de viewport.
/ ** * Detecteren of het doelelement zichtbaar is. * http://stackoverflow.com/q/123999/ * * @return Boolean 'true' als het element in de viewport staat en 'false' als dit niet het geval is. * / 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 ); ,Een functie die een functie-uitvoering stopt; bekend als ontdendering. Zoals eerder vermeld, zullen we te maken hebben met scrollen door de gebruiker, wat heel vaak gebeurt. Dus een functie binnen de
rol
gebeurtenis zal vaak worden uitgevoerd, na het scrollen van de gebruiker, waardoor de scrollervaring op de site traag of laggy wordt.De bovenstaande debounce-functie zal de uitvoeringsfrequentie verminderen. Het zal wachten op de aangegeven tijd, via de
Wacht
parameter, nadat de gebruiker stopt met scrollen voordat de functie wordt uitgevoerd./ ** * Retourneert een functie die, zolang deze blijft worden opgeroepen, niet wordt geactiveerd door b *. * De functie wordt aangeroepen nadat deze gedurende N milliseconden niet meer wordt aangeroepen. * Als onmiddellijk wordt gepasseerd, activeert u de functie aan de voorkant, in plaats van * de volgbeweging. * * @link https://davidwalsh.name/function-debounce * @link http://underscorejs.org/docs/underscore.html#section-83 * * @param Function func Functie om te weigeren * @param Integer wait De tijd in ms voor de Function-run * @param Boolean onmiddellijk * @return Void * / isDebounced: function (func, wait, immediate) var timeout; return-functie () var context = this, args = arguments; var later = function () timeout = null; if (! immediate) func.apply (context, args); ; var callNow = onmiddellijke &&! timeout; clearTimeout (time-out); timeout = setTimeout (later, wacht); if (callNow) func.apply (context, args); ; ,Een functie die bepaalt of een bewerking moet worden voortgezet of afgebroken.
/ ** * Of u doorgaat (of niet) om een nieuw artikel op te halen. * @return Boolean [description] * / isProceed: function () if (articleFetching // controleer of we momenteel een nieuwe inhoud ophalen. || articleEnding // controleer of er geen artikel meer is om te laden. ||! isVisible (this.siteFloor) // controleer of de gedefinieerde "verdieping" zichtbaar is.) retour; if (this.getNextArticle (). id <= 0 ) articleEnding = true; return; return true; ,We zullen de voorgaande functie gebruiken,
isProceed ()
, om te onderzoeken of aan alle voorwaarden is voldaan om nieuwe inhoud te verzamelen. Als dit het geval is, wordt de volgende functie uitgevoerd, waarbij de nieuwe inhoud wordt opgehaald en toegevoegd na het laatste artikel./ ** * Functie om een nieuw artikel op te halen en toe te voegen. * @return Void * / fetch: function () // Zal doorgaan of niet? if (! 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;) / ** * Wanneer het verzoek is voltooid en het haalt de inhoud * op, we voegen de inhoud toe. * / .done (functie (res) $ articleLast .after (function () if (! res) return; return $ (res) .find ("#" + main.id) .html (); );) / ** * Wanneer de functie voltooid is, of het nu 'faalt' of 'klaar' is, * zet altijd het 'articleFetching' op false. * Het geeft aan dat we klaar zijn met het ophalen van de nieuwe inhoud. * / .always (function () articleFetching = false;); ,Voeg deze functie toe binnen de
in het
. Dus de functie zal worden uitgevoerd zodra de plug-in is geïnitialiseerd en vervolgens de nieuwe inhoud ophalen wanneer aan de voorwaarden is voldaan.init: function () this.siteFloor = $ (this.settings.floor); // selecteer het element dat is ingesteld als de sitevloer. this.fetch (); ,Vervolgens voegen we een functie toe om de browsergeschiedenis te wijzigen met de History Web API. Deze specifieke functie is eerder complexer dan onze voorgaande functies. Het lastige hier is wanneer we precies de geschiedenis moeten wijzigen tijdens de gebruikersrol, de documenttitel en de URL. Het volgende is een illustratie om het idee achter de functie te vereenvoudigen:
Zoals je kunt zien op de figuur, hebben we drie regels: "daklijn", "middenlijn" en "vloerlijn" die de positie van het artikel binnen het kijkvenster illustreren. De afbeelding laat zien dat de onderkant van het eerste artikel, evenals de bovenkant van het tweede artikel, nu in het midden van de regel staat. Het geeft niet specifiek de intentie van de gebruiker aan naar welk artikel ze kijken; is het de eerste post of is het de tweede post? Daarom zouden we de browsergeschiedenis niet wijzigen wanneer twee artikelen zich op deze positie bevinden.
We zullen de geschiedenis registreren in de volgende post wanneer het artikel bovenaan de "daklijn" bereikt, omdat het meeste van het zichtbare deel van het kijkvenster nodig is.
We noteren de geschiedenis van de vorige post wanneer de onderkant ervan de "vloer-lijn" raakt, op dezelfde manier, aangezien het nu het grootste deel van het zichtbare deel van de kijker neemt.
Dit is de "while" -code die je moet toevoegen:
init: function () this.roofLine = Math.ceil (window.innerHeight * 0.4); // stel de daklijn in; this.siteFloor = $ (this.settings.floor); this.fetch (); , / ** * De browsegeschiedenis wijzigen. * @return Void * / history: function () if (! window.History.enabled) return; this.getArticles () .each (functie (index, artikel) 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)); ,Ten slotte creëren we een functie die het
ophalen ()
en degeschiedenis()
wanneer de gebruiker de pagina scrolt. Om dit te doen, creëren we een nieuwe functie genaamdrolhouder ()
, en voer het uit op de initialisatie van de plug-in./ ** * Functies die moeten worden uitgevoerd tijdens het scrollen. * @return Void * / scroller: function () window.addEventListener ("scroll", this.isDebounced (function () this.fetch (); this.history ();, 300). bind (this ), false);En zoals je hierboven kunt zien, wij ontdendering deze zoals het uitvoeren van AJAX en het veranderen van de browsergeschiedenis zijn een dure operatie.
Een tijdelijke aanduiding voor inhoud toevoegen
Dit is optioneel, maar wordt aanbevolen om de gebruikerservaring te respecteren. De tijdelijke aanduiding geeft feedback aan de gebruiker, wat aangeeft dat er een nieuw artikel op komst is.
Eerst maken we de sjabloon voor tijdelijke aanduidingen. Meestal wordt dit soort sjabloon achter de voettekst van de site geplaatst.
Houd er rekening mee dat het artikel in de tijdelijke aanduiding, de structuur, moet lijken op de echte inhoud van uw blog. Pas de HTML-structuur dienovereenkomstig aan.
De placeholder-stijlen zijn eenvoudiger. Het bevat alle basisstijlen om het te presenteren zoals het eigenlijke artikel, de animatie
@keyframe
die de laadzin simuleert, en de stijl om de zichtbaarheid in te schakelen (de tijdelijke aanduiding is aanvankelijk verborgen; deze wordt alleen weergegeven als het bovenliggende element deophalen
klasse)..tijdelijke aanduiding color: @ gray-light; padding-top: 60px; padding-bottom: 60px; border-top: 6px solid @ grijs-aansteker; Geen weergeven; .fetching & display: block; p display: block; hoogte: 20 px; achtergrond: @ grijs-licht; & __ header animation-delay: .1s; h1 hoogte: 30px; achtergrondkleur: @ grijs-licht; & __ p-1 animation-delay: .2s; breedte: 80%; & __ p-2 animation-delay: .3s; breedte: 70%;Vervolgens werken we een paar regels bij om de tijdelijke aanduiding tijdens het AJAX-verzoek als volgt weer te geven.
/ ** * Initialiseren. * @return Void * / init: function () this.roofLine = Math.ceil (window.innerHeight * 0.4); this.siteFloor = $ (this.settings.floor); this.addPlaceholder (); this.fetch (); this.scroller (); , / ** * Voeg de addPlaceholder toe. * Tijdelijke aanduiding wordt gebruikt om aan te geven dat een nieuw bericht wordt geladen. * @return Void * / addPlaceholder: function () var tmplPlaceholder = document.getElementById ("tmpl-placeholder"); tmplPlaceholder = tmplPlaceholder.innerHTML; $ (this.element) .append (tmplPlaceholder); , / ** * Functie om een nieuw artikel op te halen en toe te voegen. * @return Void * / fetch: function () ... // Selecteer het element dat het artikel omwikkelt. var main = this.element; $ .ajax (... beforeSend: function () ... // Voeg de 'fetching'-klasse toe. $ (main) .addClass (function () retourneer "ophalen";);) ... always (function () ... // Verwijder de klasse 'fetching'. $ (Main) .removeClass (function () retourneer "ophalen";););Dat is hoe we omgaan met de tijdelijke aanduiding! Onze plug-in is voltooid en het is het moment om de plug-in te implementeren.
Deployment
Het implementeren van de plug-in is vrij eenvoudig. We wijzen het element aan dat ons blogartikel omwikkelt en bellen onze plug-in met de opties als volgt.
$ (document) .ready (function () $ ("#main") .keepScrolling (floor: "#footer", artikel: ".article", data: ["id": 1, "address": "post-one", "title": "Post One", "id": 2, "address": "post-two", "title": "Post Two", "id": 3, "adres": "post-three", "title": "Post Three", "id": 4, "address": "post-four", "title": "Post Four", "id ": 5," address ":" post-five "," title ":" Post Five "]););De oneindige scroll zou nu moeten werken.
Waarschuwing: de knop Terug
In deze zelfstudie hebben we een oneindige scroll-ervaring opgebouwd; iets dat we vaak tegenkwamen op nieuwssites zoals Quartz, TechInAsia en in veel mobiele applicaties.
Hoewel bewezen is dat het een effectieve manier is om gebruikersbetrokkenheid te behouden, heeft het ook een keerzijde: het breekt de "Terug" knop in de browser. Wanneer u op de knop klikt, wordt u niet altijd nauwkeurig teruggeleid naar de vorige bezochte inhoud of pagina.
Websites pakken dit probleem op verschillende manieren aan; Quartz, bijvoorbeeld, zal u doorverwijzen naar de genoemd URL; de URL die u eerder hebt bezocht, maar niet de URL die is geregistreerd via de Web History API. TechInAsia brengt u eenvoudig terug naar de startpagina.
Afsluiten
Deze tutorial is lang en behandelt veel dingen! Sommigen van hen zijn gemakkelijk te begrijpen, terwijl sommige stukken niet zo gemakkelijk te verteren zijn. Om te helpen, heb ik een lijst met referenties samengesteld als aanvulling op dit artikel.
- Het manipuleren van de browsergeschiedenis
- Mooie, vloeiende paginaovergangen met de History Web API
- Kan iemand de â € œuitdagingâ € uitleggen functie in Javascript
- AJAX voor front-end ontwerpers
- Ontwikkeling van jQuery-plug-ins: beste praktijken
Ten slotte, bekijk de volledige broncode en bekijk de demo!