Dit is iets dat de twitterpersoon te zeggen had.
jQuery is een geweldige tool, maar is er ooit een tijd dat je het niet zou moeten gebruiken? In deze zelfstudie gaan we bekijken hoe we een aantal interessante scrolgedragingen met jQuery kunnen opbouwen en bekijken we hoe ons project mogelijk kan worden verbeterd door zoveel mogelijk jQuery of abstractie te verwijderen..
Soms kan je code zelfs nog meer zijn? Magisch? door een deel van die goedheid van jQuery af te trekken.
Je zou verwachten dat dit je normale doe-iets-geweldig-met-jQuery zelfstudie is. Eigenlijk is het dat niet. Hoewel je uiteindelijk een nogal cool, maar eerlijk gezegd, misschien even nutteloos effect kunt opbouwen, is dat niet het belangrijkste punt dat ik wil dat je weghaalt uit deze tutorial.
Zoals je hopelijk zult zien, wil ik dat je leert om te kijken naar de jQuery die je aan het schrijven bent als gewoon JavaScript, en besef dat er niets magisch aan is. Soms kan je code zelfs nog meer zijn? Magisch? door een deel van die goedheid van jQuery af te trekken. Hopelijk ben je tegen het einde een beetje beter in het ontwikkelen met JavaScript dan toen je begon.
Als dat te abstract klinkt, beschouw dit dan als een les in performance en code refactoring? en ook als ontwikkelaar buiten je comfortzone stappen.
Dit is wat we gaan bouwen. Ik heb deze inspiratie gekregen van de relatief nieuwe Twitter voor Mac-app. Als je de app hebt (het is gratis), ga dan iemands accountpagina bekijken. Terwijl je scrolt, zie je dat ze geen avatar links van elke tweet hebben; de avatar voor de eerste tweet? volgt? jij terwijl je naar beneden scrolt. Als u een retweet tegenkomt, ziet u dat de avatar van de geretweverde persoon op de juiste manier naast zijn of haar tweet is geplaatst. Vervolgens, wanneer de tweets van de retweeter opnieuw beginnen, neemt hun avatar het over.
Dit is de functionaliteit die ik wilde bouwen. Met jQuery zou dit niet al te moeilijk zijn om samen te stellen, dacht ik. En zo begon ik.
Voordat we bij de ster van de show kunnen komen, hebben we natuurlijk wat markeringen nodig om mee te werken. Ik zal hier niet veel tijd doorbrengen, want het is niet het belangrijkste punt van deze tutorial:
Twitter Avatar scrollen Dit is iets dat de twitterpersoon te zeggen had.
Dit is iets dat de twitterpersoon te zeggen had.
Dit is iets dat de twitterpersoon te zeggen had.
Dit is iets dat de twitterpersoon te zeggen had.
Dit is iets dat de twitterpersoon te zeggen had.
Dit is iets dat de twitterpersoon te zeggen had.
Dit is iets dat de twitterpersoon te zeggen had.
Ja, het is groot.
Hoe zit het met wat CSS? Probeer dit:
lichaam font: 13px / 1.5 "helvetica neue", helvetica, arial, san-serif; artikel display: block; achtergrond: #ececec; width: 380px; padding: 10px; margin: 10px; overloop verborgen; artikel img float: left; artikel p marge: 0; padding-left: 60px;
Vrij minimaal, maar het zal ons geven wat we nodig hebben.
Nu, op naar de JavaScript!
Ik denk dat het redelijk is om te zeggen dat dit niet je gemiddelde JavaScript-widgetwerk is; het is een beetje ingewikkelder. Hier zijn slechts een paar dingen waar u rekening mee moet houden:
Wanneer je begint met het schrijven van iets, maak je je zorgen om het alleen maar te laten werken; optimaliseren kan later komen. Versie één negeerde verschillende belangrijke praktische tips van jQuery. Waar we hier beginnen is versie twee: de geoptimaliseerde jQuery-code.
Ik besloot dit als een jQuery-plug-in te schrijven, dus de eerste stap is om te beslissen hoe het zal worden aangeroepen; Ik ging met dit:
$ (wrapper_element) .twitter_scroll (entries_element, unscrollable_element);
Het jQuery-object dat we de plug-in noemen, verpakt de? Tweets? (of waar je ook doorheen scrolt). De eerste parameter die de plug-in neemt, is een selector voor de elementen die zullen scrollen: de? Tweets.? De tweede selector is voor de elementen die indien nodig op hun plaats blijven (de plug-in verwacht dat dit afbeeldingen zijn, maar het zou niet veel aanpassingen vergen om het voor andere elementen te laten werken). Dus voor de HTML die we hierboven hadden, noemen we de plug-in als volgt:
$ ("sectie"). twitter_scroll ("article", ".avatar"); // OF $ ("sectie"). Twitter_scroll (". Avatar");
Zoals u zult zien wanneer we bij de code komen, is de eerste parameter optioneel; als er maar één parameter is, nemen we aan dat het de niet-selecteerbare selector is en de invoer de directe ouders van de unscrollables (ik weet het, unscrollables is een slechte naam, maar het is het meest generieke wat ik zou kunnen bedenken).
Dus, hier is onze plug-in shell:
(functie ($) jQuery.fn.twitter_scroll = function (entries, uncrollable) ; (jQuery));
Vanaf nu gaat alle JavaScript die we bekijken hierheen.
Laten we beginnen met de setup-code; er is wat werk aan de winkel voordat we de scroll-handler instellen.
if (! unscrollable) unscrollable = $ (entries); entries = unscrollable.parent (); else unscrollable = $ (unscrollable); entries = $ (entries);
Ten eerste, de parameters: Als unscrollable
is een false-y-waarde, dan stellen we deze in op? jQuerified? entries
selector en stel in entries
aan de ouders van unscrollable
. Anders zullen we? JQuerify? beide parameters. Het is belangrijk om op te merken dat nu (als de gebruiker zijn markup correct heeft gedaan, wat we zullen moeten aannemen dat ze die hebben), we twee jQuery-objecten hebben waarin de overeenkomende items dezelfde index hebben: dus unscrollable [i]
is het kind van entries [i]
. Dit zal later nuttig zijn. (Notitie: als we niet wilden aannemen dat de gebruiker zijn document correct had gemarkeerd, of dat ze selectors gebruikten om elementen vast te leggen buiten wat we wilden, zouden we kunnen gebruiken deze
als de contextparameter, of gebruik de vind
methode van deze
.)
Laten we vervolgens een aantal variabelen instellen; normaal zou ik dit bovenaan doen, maar er zijn er meerdere afhankelijk van unscrollable
en entries
, dus dat hebben we eerst aangepakt:
var parent_from_top = this.offset (). top, entries_from_top = entries.offset (). top, img_offset = unscrollable.offset (), prev_item = null, starter = false, margin = 2, anti_repeater = null, win = $ (window ), scroll_top = win.scrollTop ();
Laten we deze doornemen. Zoals je je misschien kunt voorstellen, is de offset van de elementen waarmee we samenwerken belangrijk; jQuery compenseren
methode retourneert een object met top
en links
offset eigenschappen. Voor het bovenliggende element (deze
in de plug-in) en de entries
, we hebben alleen de bovenste offset nodig, dus we zullen dat krijgen. Voor de unscrollables hebben we zowel bovenaan als links nodig, dus we zullen hier niets specifieks krijgen.
De variabelen prev_item
, beginner
, en anti_repeater
wordt later gebruikt, buiten de scrolhandler of in de scrol-handler, waar we waarden nodig hebben die blijven bestaan in de roepingen van de handler. Tenslotte, winnen
zal op een paar plaatsen worden gebruikt, en scroll_top
is de afstand van de schuifbalk vanaf de bovenkant van het venster; we gebruiken dit later om te bepalen in welke richting we scrollen.
Vervolgens gaan we bepalen welke elementen de eerste en de laatste zijn in strepen van? Tweets.? Er zijn waarschijnlijk een aantal manieren om dit te doen; we gaan dit doen door een HTML5-dataattribuut toe te passen op de juiste elementen.
entries.each (functie (i) var img = unscrollable [i]; if ($ .contains (this, img)) if (img.src === prev_item) img.style.visibility = "hidden"; if (starter) entries [i-1] .setAttribute ("data-8088-starter", "true"); starter = false; if (! entries [i + 1]) entries [i] .setAttribute ( "data-8088-ender", "true"); else prev_item = img.src; starter = true; if (entries [i-1] && unscrollable [i-1] .style.visibility === " hidden ") entries [i-1] .setAttribute (" data-8088-ender "," true ");); prev_item = null;
We gebruiken jQuery's elk
methode op de entries
voorwerp. Onthoud dat we binnenin de functie passeren, deze
verwijst naar het huidige element van entries
. We hebben ook de indexparameter die we zullen gebruiken. We beginnen met het ophalen van het overeenkomstige element in de unscrollable
object en bewaar het in img
. Als onze invoer dat element bevat (dit zou moeten, maar we controleren het alleen), zullen we controleren of de bron van de afbeelding hetzelfde is als prev_item
; als dat zo is, weten we dat deze afbeelding hetzelfde is als die in het vorige item. Daarom zullen we de afbeelding verbergen; we kunnen de display-eigenschap niet gebruiken, omdat die deze uit de stroom van het document zou verwijderen; we willen niet dat andere elementen op ons afkomen.
Dan, als beginner
is waar, we geven de invoer voor dit is het attribuut data-8088-starter
; onthoud dat alle HTML5-gegevenskenmerken moeten beginnen met? data- ?; en het is een goed idee om uw eigen voorvoegsel toe te voegen, zodat u niet in conflict komt met de code van andere ontwikkelaars (mijn voorvoegsel is 8088, u zult uw eigen voorvoegsel moeten vinden :)). HTML5-gegevenskenmerken moeten strings zijn, maar in ons geval zijn het niet de gegevens waar we ons zorgen over maken; we moeten alleen dat element markeren. Toen gingen we zitten beginner
naar false. Tot slot, als dit het laatste item is, markeren we het als een einde.
Als de bron van de afbeelding niet hetzelfde is als de bron van de vorige afbeelding, worden we opnieuw ingesteld prev_item
met de bron van uw huidige afbeelding; dan zetten we de starter op waar
. Op die manier kunnen we, als we vinden dat de volgende afbeelding dezelfde bron heeft als deze, deze markeren als de starter. Tenslotte, als er een item voor deze is en de bijbehorende afbeelding is verborgen, weten we dat invoer het einde van een reeks is (omdat deze een andere afbeelding heeft). Daarom geven we het een gegevenskenmerk dat het als een einde markeert.
Als we klaar zijn, gaan we verder prev_item
naar nul
; we zullen het binnenkort opnieuw gaan gebruiken.
Als u nu kijkt naar de vermeldingen in Firebug, zou u zoiets als dit moeten zien:
Nu zijn we klaar om aan die schuifhandler te werken. Er zijn twee delen aan dit probleem. Eerst vinden we het item dat zich het dichtst bij de bovenkant van de pagina bevindt. Ten tweede doen we wat geschikt is voor de afbeelding met betrekking tot die vermelding.
$ (document) .bind ("scroll", functie (e) var temp_scroll = win.scrollTop (), down = ((scroll_top - temp_scroll) < 0) ? true : false, top_item = null, child = null; scroll_top = temp_scroll; // more coming );
Dit is onze rolbehandelingsshell; we hebben een aantal variabelen waarmee we beginnen; let nu op naar beneden
. Dit zal waar zijn als we naar beneden scrollen, onwaar als we naar boven scrollen. Vervolgens worden we opnieuw ingesteld scroll_top
om de afstand te zijn van de top waar we naar toe scrollen.
Laten we nu beginnen top_item
om de invoer te zijn die zich het dichtst bij de bovenkant van de pagina bevindt:
top_item = entries.filter (functie (i) var distance = $ (this) .offset (). top - scroll_top; return (afstand < (parent_from_top + margin) && distance > (parent_from_top - margin)); );
Dit is helemaal niet moeilijk; we gebruiken gewoon de filter
methode om te beslissen aan welk item we willen toewijzen top_item
. Eerst krijgen we de afstand
door het bedrag dat we hebben gescrolld af te trekken van de bovenste offset van het item. Dan keren we terug waar als afstand
is tussen parent_from_top + marge
en parent_from_top - margin
; anders, false. Als dit je in verwarring brengt, denk er dan op deze manier over: we willen terugkeren naar waar wanneer een element zich recht bovenin het venster bevindt; in dit geval, afstand
zou gelijk zijn aan 0. We moeten echter rekening houden met de bovenste offset van de container die onze? tweets bevat? dus we willen het echt afstand
gelijk maken parent_from_top
.
Maar het is mogelijk dat onze scrol-handler niet vuurt wanneer we precies op die pixel staan, maar in plaats daarvan wanneer we er dichtbij zijn. Ik ontdekte dit toen ik de afstand registreerde en vond dat het waarden waren die een halve pixel uit waren; ook, als je scrolafhandelingsfunctie niet al te efficiënt is (wat deze niet zal zijn, nog niet), is het mogelijk dat er niet op wordt gebrand elk scrol event. Om ervoor te zorgen dat u er een in het juiste gebied krijgt, voegen we een marge toe of trekken deze af om ons een klein bereik te geven om binnen te werken.
Nu we het bovenste item hebben, laten we er iets mee doen.
if (top_item) if (top_item.attr ("data-8088-starter")) if (! anti_repeater) child = top_item.children (unscrollable.selector); anti_repeater = child.clone (). appendTo (document.body); child.css ("visibility", "hidden"); anti_repeater.css ('positie': 'fixed', 'top': img_offset.top + 'px', 'left': img_offset.left + "px"); else if (top_item.attr ("data-8088-ender")) top_item.children (unscrollable.selector) .css ("visibility", "visible"); if (anti_repeater) anti_repeater.remove (); anti_repeater = null; if (! down) if (top_item.attr ("data-8088-starter")) top_item.children (unscrollable.selector) .css ("visibility", "visible"); if (anti_repeater) anti_repeater.remove (); anti_repeater = null; else if (top_item.attr ("data-8088-ender")) child = top_item.children (unscrollable.selector); anti_repeater = child.clone (). appendTo (document.body); child.css ("visibility", "hidden"); anti_repeater.css ('positie': 'fixed', 'top': img_offset.top + 'px', 'left': img_offset.left + "px");
U zult merken dat de bovenstaande code bijna tweemaal hetzelfde is; onthoud dat dit de niet-geoptimaliseerde versie is. Terwijl ik het probleem langzaamaan doorhad, begon ik met het uitzoeken hoe ik naar beneden kon scrollen; toen ik dat eenmaal had opgelost, werkte ik eraan om omhoog te scrollen. Het was niet meteen duidelijk, totdat alle functionaliteit aanwezig was, dat er zoveel gelijkenis zou zijn. Vergeet niet dat we snel zullen optimaliseren.
Dus laten we dit ontleden. Als het bovenste item een heeft data-8088-starter
attribuut, laten we dan kijken of het anti_repeater
is vastgesteld; dit is de variabele die naar het afbeeldingselement wijst dat zal worden gerepareerd terwijl de pagina scrolt. Als anti_repeater
niet is ingesteld, dan zullen we het kind van ons krijgen top_item
invoer met dezelfde selector als unscrollable
(nee, dit is geen slimme manier om dit te doen, we zullen het later verbeteren). Dan kloonen we dat en voegen we het toe aan het lichaam. We zullen die verstoppen en de gekloonde precies positioneren waar hij moet gaan.
Als het element geen a heeft data-8088-starter
kenmerk, we zullen controleren op een data-8088-ender
attribuut. Als dat er is, vinden we het juiste kind en maken het zichtbaar en verwijderen we het anti_repeater
en stel die variabele in op nul
.
Gelukkig, als we niet naar beneden gaan (als we omhoog gaan), is het precies het tegenovergestelde van onze twee attributen. En, als het top_item
heeft geen van beide attributen, we zitten ergens in het midden van een streep en hoeven niets te veranderen.
Welnu, deze code doet wat we willen; als je het echter eens probeert, zul je merken dat je moet scrollen erg traag om het goed te laten werken. Probeer de regels toe te voegen console.profile ( "scroll")
en console.profileEnd ()
als de eerste en laatste regels van de functie voor scrollen. Voor mij duurt de handler tussen de 2.5ms - 4ms, met 166 - 170 functieaanroepen die plaatsvinden.
Dat is veel te lang voor een scroll-handler om te rennen; en, zoals je je misschien kunt voorstellen, ik bestuur dit op een redelijk goed uitgeruste computer. Merk op dat sommige functies 30-31 keer worden genoemd; we hebben 30 vermeldingen waarmee we werken, dus dit is waarschijnlijk een onderdeel van het doorlopen van alle gegevens om de beste te vinden. Dit betekent dat hoe meer items we hebben, hoe langzamer dit gaat lopen; zo inefficiënt! Dus we moeten zien hoe we dit kunnen verbeteren.
Als je vermoedt dat jQuery hier de hoofdschuldige is, heb je gelijk. Hoewel frameworks als jQuery ontzettend handig zijn en het werken met de DOM een fluitje van een cent maken, komen ze met een compromis: performance. Ja, ze worden altijd beter; en ja, dat geldt ook voor de browsers. Echter, onze situatie vraagt om de snelst mogelijke code, en in ons geval zullen we een beetje jQuery moeten opgeven voor een aantal directe DOM-werkzaamheden die niet al te veel moeilijker zijn.
Laten we met het voor de hand liggende deel: wat we doen met de top_item
zodra we het hebben gevonden. Momenteel, top_item
is een jQuery-object; echter, alles wat we doen met jQuery met top_item
is trivially moeilijker zonder jQuery, dus we doen het? raw.? Wanneer we het verkrijgen van beoordelen top_item
, we zorgen ervoor dat het een onbewerkt DOM-element is.
Dus, hier is wat we kunnen veranderen om het sneller te maken:
getAttribute
methode, in plaats van jQuery's attr
.unscrollable
dat komt overeen met de top_item
invoer, in plaats van gebruiken unscrollable.selector
.clodeNode
en appendChild
methoden, in plaats van de jQuery-versies.stijl
eigenschap in plaats van jQuery's css
methode.removeNode
in plaats van jQuery's verwijderen
.Door deze ideeën toe te passen, eindigen we hiermee:
if (top_item) if ((down && top_item.getAttribute ("data-8088-starter")) || (! down && top_item.getAttribute ("data-8088-ender"))) if (! anti_repeater) child = unscrollable [entries.indexOf (top_item)]; anti_repeater = child.cloneNode (false); document.body.appendChild (anti_repeater); child.style.visibility = "verborgen"; style = anti_repeater.style; style.position = 'vast'; style.top = img_offset.top + 'px'; style.left = img_offset.left + 'px'; if ((down && top_item.getAttribute ("data-8088-ender")) || (! down && top_item.getAttribute ("data-8088-starter"))) unscrollable [entries.indexOf (top_item)] .style.visibility = "zichtbaar"; if (anti_repeater) anti_repeater.parentNode.removeChild (anti_repeater); anti_repeater = null;
Dit is een veel betere code: niet alleen wordt die verdubbeling verwijderd, maar er wordt ook absoluut geen jQuery gebruikt. (Terwijl ik dit schrijf, denk ik dat het misschien zelfs een beetje sneller is om een CSS-klasse toe te passen om de styling te doen, daar kun je mee experimenteren!)
Je zou denken dat dat zo goed is als we kunnen krijgen; er is echter een serieuze optimalisatie die kan plaatsvinden bij het verkrijgen van top_item
. Momenteel gebruiken we jQuery's filter
methode. Als je erover nadenkt, is dit ongelooflijk slecht. We weten dat we slechts één item van deze filtering terug krijgen; maar de filter
functie weet dat niet, dus het blijft elementen door onze functie rennen nadat we degene hebben gevonden die we willen. We hebben 30 elementen in entries
, dus dat is een behoorlijk grote verspilling van tijd. Wat we willen doen is dit:
for (i = 0; entries [i]; i ++) distance = $ (entries [i]). offset (). top - scroll_top; als (afstand < (parent_from_top + margin) && distance > (parent_from_top - margin)) top_item = entries [i]; breken;
(Afwisselend kunnen we een while-lus gebruiken, met de voorwaarde !top_item
; hoe dan ook, het maakt niet veel uit.)
Op deze manier, zodra we de top_item
, we kunnen stoppen met zoeken. We kunnen het echter beter; omdat scrollen lineair is, kunnen we voorspellen welk item het dichtst bij de volgende staat. Als we naar beneden scrollen, is het hetzelfde item als de vorige keer of het volgende item. Als we naar boven scrollen, is dit hetzelfde item als de vorige keer of het item ervoor. Dit zal handig zijn naarmate de pagina verder wordt geopend, omdat de for-lus altijd bovenaan de pagina begint.
Dus hoe kunnen we dit implementeren? Nou, we moeten beginnen met het bijhouden van wat de top_item
was tijdens het vorige gebruik van de scroll-handler. We kunnen dit doen door toe te voegen
prev_item = top_item;
helemaal aan het einde van de scroll-verwerkingsfunctie. Nu, waar we eerder het waren top_item
, zet dit:
if (prev_item) prev_item = $ (prev_item); hoogte = hoogte || prev_item.outerHeight (); if (down && prev_item.offset (). top - scroll_top + height < margin) top_item = entries[ entries.indexOf(prev_item[0]) + 1]; else if ( !down && (prev_item.offset().top - scroll_top - height) > (margin + parent_from_top)) top_item = entries [entries.indexOf (prev_item [0]) - 1]; else top_item = prev_item [0]; else for (i = 0; entries [i]; i ++) distance = $ (entries [i]). offset (). top - scroll_top; als (afstand < (parent_from_top + margin) && distance > (parent_from_top - margin)) top_item = entries [i]; breken;
Dit is absoluut een verbetering; als er een was prev_item
, dan kunnen we dat gebruiken om het volgende te vinden top_item
. De wiskunde wordt hier een beetje lastig, maar dit is wat we aan het doen zijn:
prev_item
+ 1 om het juiste element te vinden in entries
).top_item
, we gebruiken onze for-lus als een fallback.Er zijn nog een paar andere zaken die hier moeten worden aangepakt. Ten eerste, wat is dit hoogte
variabele? Welnu, als al onze ingaven dezelfde hoogte hebben, kunnen we voorkomen dat we de hoogte elke keer binnen de scroll-handler berekenen door het in de setup te doen. Dus ik heb dit aan de setup toegevoegd:
height = entries.outerHeight (); if (entries.length! == entries.filter (function () return $ (this) .outerHeight () === height;). length) height = null;
jQuery outerHeight
methode krijgt de hoogte van een element, inclusief opvulling en randen. Als alle elementen dezelfde hoogte hebben als de eerste, dan hoogte
zal worden ingesteld; anders- hoogte
zal nul zijn. Dan, wanneer het wordt top_item
, we kunnen gebruiken hoogte
als het is ingesteld.
Het andere ding om op te merken is dit: je zou kunnen denken dat het inefficiënt is om te doen prev_item.offset (). bovenkant
tweemaal; zou dat niet binnen een variabele moeten gaan. Eigenlijk doen we het maar één keer, omdat de tweede if-verklaring alleen wordt aangeroepen als naar beneden
is fout; omdat we de logische operator AND gebruiken, worden de tweede delen van de twee if-instructies nooit beide op dezelfde functieaanroep uitgevoerd. Je zou het natuurlijk in een variabele kunnen stoppen als je denkt dat het je code schoner houdt, maar het doet niets voor de prestaties.
Ik ben echter nog steeds niet tevreden. Natuurlijk, onze scrolhandler is nu sneller, maar we kunnen het beter maken. We gebruiken jQuery alleen voor twee dingen: om de outerHeight
van prev_item
en om de bovenste offset van te krijgen prev_item
. Voor zoiets kleins is het vrij duur om een jQuery-object te maken. Er is echter geen onmiddellijk-voor de hand liggende, niet-jQuery-oplossing, zoals er eerder was. Dus, laten we een duik nemen in de jQuery-broncode om precies te zien wat jQuery aan het doen is; op deze manier kunnen we de bits eruit halen die we nodig hebben zonder het dure gewicht van jQuery zelf te gebruiken.
Laten we beginnen met het probleem van de topoffset. Hier is de jQuery-code voor de compenseren
methode:
functie (opties) var elem = this [0]; if (! elem ||! elem.ownerDocument) return null; if (options) return this.each (function (i) jQuery.offset.setOffset (this, options, i);); if (elem === elem.ownerDocument.body) return jQuery.offset.bodyOffset (elem); var box = elem.getBoundingClientRect (), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement, clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, top = box.top + (self.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop) - clientTop, left = box.left + (self.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft; return top: top, left: left;
Dit ziet er ingewikkeld uit, maar het is niet slecht; we hebben alleen de bovenste offset nodig, niet de linker offset, dus we kunnen deze eruit halen en gebruiken om onze eigen functie te creëren:
functie get_top_offset (elem) var doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement; return elem.getBoundingClientRect (). top + (self.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop) - docElem.clientTop || body.clientTop || 0;
Alles wat we moeten doen, gaat door in het onbewerkte DOM-element, en we krijgen de topcompensatie terug; Super goed! zodat we al onze kunnen vervangen offset (). bovenkant
oproepen met deze functie.
Hoe zit het met outerHeight
? Deze is een beetje lastiger omdat het een van die multi-use interne jQuery-functies gebruikt.
functie (marge) deze [0] retourneren? jQuery.css (dit [0], type, false, margin? "margin": "border"): null;
Zoals we kunnen zien, belt dit eigenlijk gewoon naar de css
functie, met deze parameters: het element,? height ?,? border ?, en vals
. Dit is hoe dat eruit ziet:
functie (elem, naam, force, extra) if (naam === "width" || name === "height") var val, props = cssShow, which = name === "width"? cssWidth: cssHeight; functie getWH () val = naam === "breedte"? elem.offsetWidth: elem.offsetHeight; if (extra === "border") ga terug; jQuery.each (welke functie () if (! extra) val - = parseFloat (jQuery.curCSS (elem, "padding" + this, true)) || 0; if (extra === "marge ") val + = parseFloat (jQuery.curCSS (elem," margin "+ this, true)) || 0; else val - = parseFloat (jQuery.curCSS (elem," border "+ this +" Width " , waar)) || 0;); if (elem.offsetWidth! == 0) getWH (); else jQuery.swap (elem, props, getWH); retourneer Math.max (0, Math.round (val)); retourneer jQuery.curCSS (elem, name, force);
We lijken op dit moment misschien hopeloos verloren, maar het is de moeite waard om de logica toch te volgen? omdat de oplossing geweldig is! Blijkbaar kunnen we alleen de elementen gebruiken offsetHeight
eigendom; dat geeft ons precies wat we willen!
Daarom ziet onze code er nu als volgt uit:
if (prev_item) height = height || prev_item.offsetHeight; if (down && get_top_offset (prev_item) - scroll_top + height < margin) top_item = entries[ entries.indexOf(prev_item) + 1]; else if ( !down && (get_top_offset(prev_item) - scroll_top - height) > (margin + parent_from_top)) top_item = entries [entries.indexOf (prev_item) - 1]; else top_item = vorige_; else for (i = 0; entries [i]; i ++) distance = get_top_offset (entries [i]) - scroll_top; als (afstand < (parent_from_top + margin) && distance > (parent_from_top - margin)) top_item = entries [i]; breken;
Nu hoeft niets een jQuery-object te zijn! En als u het naar beneden haalt, zou u ver onder een milliseconde moeten zijn en slechts een paar functieaanroepen hebben.
Voordat we concluderen, is hier een handige tip om in jQuery rond te graven, zoals we hier deden: James Padolsey's jQuery Source Viewer gebruiken. Het is een geweldige tool die het ongelooflijk eenvoudig maakt om in de code te graven en te zien wat er aan de hand is zonder al te overweldigend te zijn. [Sid's note: nog een geweldige tool die je moet afrekenen - jQuery Deconstructer.]
Ik hoop dat je deze tutorial leuk vond! Je hebt misschien geleerd hoe je een nogal netjes scrolgedrag kunt creëren, maar dat was niet het belangrijkste punt. Neem deze punten weg van deze tutorial: