Implementați deplasarea pe Twitter fără jQuery

jQuery este un instrument minunat, dar există vreodată un moment în care nu ar trebui să-l utilizați? În acest tutorial, vom examina cum să construim un comportament interesant de defilare cu jQuery și apoi să analizăm modul în care proiectul nostru ar putea fi îmbunătățit prin eliminarea cât mai multor jQuery sau abstractizări.


Intro: Planul

Uneori, codul dvs. poate fi chiar mai mult? prin scăderea oarecare din bunătatea jQuery.

S-ar putea să te aștepți ca acest lucru să fie tutorialul tău obișnuit de a face-ceva-minunat-cu-jQuery. De fapt, nu este. În timp ce s-ar putea să ajungeți la construirea unui efect destul de răcoros - dar, sincer, poate la fel de inutil, acest lucru nu este punctul principal pe care vreau să îl îndepărtați de acest tutorial.

După cum veți vedea, vă rog să învățați să vă uitați la jQuery pe care îl scrieți doar ca regulat JavaScript și să vă dați seama că nu este nimic magic despre asta. Uneori, codul dvs. poate fi chiar mai mult? prin scăderea oarecare din bunătatea jQuery. Sperăm că până la sfârșitul acestui lucru veți fi puțin mai bine să dezvoltați cu JavaScript decât atunci când ați început.

Dacă aceasta pare prea abstractă, considerați aceasta o lecție în performanța și refactorizarea codului? și, de asemenea, pas cu pas în afara zonei dvs. de confort ca dezvoltator.


Pasul 1: Proiectul

Iată ce vom construi. Am această inspirație din aplicația relativ nouă Twitter pentru Mac. Dacă aveți aplicația (este gratuită), accesați pagina contului cuiva. Pe măsură ce defilați în jos, veți vedea că nu au un avatar în stânga fiecărui tweet; avatarul pentru primul tweet? când defilați în jos. Dacă întâlniți un retweet, veți vedea că avatarul persoanei retumate este plasat în mod corespunzător alături de tweet-ul său. Apoi, când tweets-ul retweeter-ului începe din nou, avatarul îi preia.

Aceasta este funcționalitatea pe care am vrut să o construiesc. Cu jQuery, acest lucru nu ar fi prea greu pentru a pune împreună, m-am gândit. Și așa am început.


Pasul 2: HTML & CSS

Desigur, înainte de a ajunge la vedeta spectacolului, avem nevoie de un markup cu care să lucrăm. Nu voi petrece mult timp aici, pentru că nu este punctul principal al acestui tutorial:

    Twitter Scrolling Avatar    

Acesta este ceva pe care persoana de twitter a trebuit să o spună.

Acesta este ceva pe care persoana de twitter a trebuit să o spună.

Acesta este ceva pe care persoana de twitter a trebuit să o spună.

Acesta este ceva pe care persoana de twitter a trebuit să o spună.

Acesta este ceva pe care persoana de twitter a trebuit să o spună.

Acesta este ceva pe care persoana de twitter a trebuit să o spună.

Acesta este ceva pe care persoana de twitter a trebuit să o spună.

Da, e mare.

Ce zici de un CSS? Incearca asta:

corp font: 13px / 1.5 "helvetica neue", helvetica, arial, san-serif;  articol display: block; fundal: #ececec; lățime: 380px; padding: 10px; margin: 10px; overflow: ascuns;  articol img float: left;  articol p marja: 0; padding-left: 60 px; 

Destul de minim, dar ne va da ceea ce avem nevoie.

Acum, la JavaScript!


Pasul 3: JavaScript, Runda 1

Cred că este corect să spun că acest lucru nu este munca dvs. medie widget JavaScript; este un pic mai complicat. Iată câteva dintre lucrurile pe care trebuie să le răspundeți:

  • Trebuie să ascundeți fiecare imagine care este aceeași cu cea din imaginea precedentă.?
  • Când pagina este derulată, trebuie să determinați ce? este cel mai aproape de partea de sus a paginii.
  • Dacă "tweet"? este primul dintr-o serie de "tweets"? de către aceeași persoană, trebuie să reparăm avatarul, astfel încât să nu se deruleze cu restul paginii.
  • În cazul în care top? Tweet? este ultima dintr-o serie de tweets de către un utilizator, trebuie să oprim avatarul la locul potrivit.
  • Toate acestea trebuie să funcționeze pentru derularea în jos și în sus a paginii.
  • Deoarece toate acestea sunt executate de fiecare dată când se declanșează un eveniment de defilare, trebuie să fie incredibil de rapidă.

Când începeți să scrieți ceva, vă faceți griji că tocmai ați lucrat; optimizarea poate veni mai târziu. Versiunea una a ignorat câteva dintre cele mai bune practici jQuery importante. Ceea ce începem aici este versiunea a doua: codul optimizat jQuery.

Am decis să scriu acest plugin ca jQuery, deci primul pas este să decideți cum va fi numit; M-am dus cu asta:

$ (wrapper_element) .twitter_scroll (input_element, unscrollable_element);

Obiectul jQuery pe care îl numim plug-in, împachetează "tweets"? (sau indiferent de ce parcurgeți). Primul parametru pe care pluginul îl are este un selector pentru elementele care se vor derula: "tweets". Cel de-al doilea selector este pentru elementele care rămân în loc când este necesar (pluginul se așteaptă ca acestea să fie imagini, dar nu ar trebui să se adapteze prea mult pentru a face să funcționeze pentru alte elemente). Deci, pentru HTML-ul pe care l-am avut de mai sus, numim plugin-ul ca acesta:

$ ("secțiune"). twitter_scroll ("articol", ".avatar"); // OR $ ("secțiune"). Twitter_scroll (". Avatar");

Așa cum veți vedea când ajungem la cod, primul parametru va fi opțional; dacă există doar un singur parametru, vom presupune că este selectorul nedorit, iar intrările sunt părinții direcți ai nescrollabilelor (știu că uncrollables este un nume rău, dar este cel mai generic lucru pe care aș putea să-l prezint).

Deci, aici este shell plugin-ul nostru:

(funcția ($) jQuery.fn.twitter_scroll = funcție (intrări, nescroll) ; (jQuery));

De acum înainte, toate JavaScript pe care le vom uita merge aici.

Setarea pluginului

Să începem cu codul de setare; există ceva de făcut înainte de a configura dispozitivul de derulare a parolei.

dacă (unscrollable) unscrollable = $ (intrări); intrări = unscrollable.parent ();  altfel unscrollable = $ (unscrollable); intrări = $ (intrări); 

În primul rând, parametrii: Dacă unscrollable este o valoare falsă, o vom pune la "jQuerified"? intrări selector și setați intrări catre parintii lui unscrollable. În caz contrar, vom "jQuerify"? ambii parametri. Este important să observăm că acum (dacă utilizatorul a făcut corect marcarea corectă, ceea ce va trebui să presupunem că are), avem două obiecte jQuery în care intrările de potrivire au același index: unscrollable [i] este copilul lui intrările [i]. Acest lucru va fi util mai târziu. (Notă: dacă nu vrem să presupunem că utilizatorul și-a marcat corect documentul sau că au folosit selectori care ar capta elemente în afara a ceea ce vrem, am putea folosi acest ca parametru de context, sau utilizați găsi Metodă de acest.)

Apoi, să stabilim câteva variabile; în mod normal, aș face asta chiar în partea de sus, dar mai multe depind unscrollable și intrări, așa că ne-am ocupat mai întâi de aceasta:

var top_from_top = this.offset () top, input_from_top = entries.offset () top, img_offset = unscrollable.offset (), prev_item = null, starter = false, margin = 2, anti_repeater = ), scroll_top = win.scrollTop ();

Să trecem prin ele. Așa cum vă puteți imagina, compensarea elementelor cu care lucrăm aici este importantă; lui jQuery ofset metoda returnează un obiect cu top și stânga proprietăți offset. Pentru elementul părinte (acest în interiorul pluginului) și intrări, vom avea nevoie doar de offset-ul de sus, așa că vom obține asta. Pentru cei rămași, vom avea nevoie atât de partea superioară, cât și de stânga, deci nu vom obține nimic specific aici.

Variabilele prev_item, incepator, și anti_repeater va fi folosit mai târziu, fie în afara dispozitivului de derulare a scroll-ului, fie în interiorul dispozitivului de derulare a parolei, unde vom avea nevoie de valori care persistă de-a lungul apelurilor operatorului. In cele din urma, victorie va fi folosit în câteva locuri și scroll_top este distanța dintre bara de derulare din partea de sus a ferestrei; vom folosi mai târziu acest lucru pentru a determina în ce direcție mergem.

Apoi, vom determina care elemente sunt primul și ultimul în șir de "tweets". Există probabil câteva moduri de a face acest lucru; vom face acest lucru prin aplicarea unui atribut de date HTML5 elementelor corespunzătoare.

()) if (img.src === prev_item) img.style.visibility = "ascuns";); dacă inputs [i-1] .setAttribute ("date-8088-starter", "true"); "date-8088-ender", "true"); altceva prev_item = img.src; starter = true; if (entries [i-1] && unscrollable [i-1] .style.visibility === " ascuns ") intrări [i-1] .setAttribute (" data-8088-ender "," adevărat ");); prev_item = null;

Folosim jQuery fiecare metoda pe intrări obiect. Amintiți-vă că în interiorul funcției trecem, acest se referă la elementul curent din intrări. De asemenea, avem parametrul index, pe care îl vom folosi. Vom începe prin a obține elementul corespunzător în unscrollable obiect și stocarea în img. Apoi, dacă intrarea noastră conține acel element (ar trebui, dar doar verificăm), vom verifica dacă sursa imaginii este identică cu cea a imaginii prev_item; dacă este, știm că această imagine este aceeași cu cea din intrarea anterioară. Prin urmare, vom ascunde imaginea; nu putem folosi proprietatea afișajului, deoarece aceasta ar elimina din fluxul documentului; noi nu vrem ca alte elemente sa se miste asupra noastra.

Atunci, dacă incepator este adevărat, vom da intrarea inainte de acest atribut Date-8088-starter; rețineți că toate atributele de date HTML5 trebuie să înceapă cu "data- ?; și este o idee bună să adăugați propriul prefix, astfel încât să nu vă confruntați cu codul altor dezvoltatori (Prefixul meu este 8088; va trebui să vă găsiți propria :)). Atributele de date HTML5 trebuie să fie șiruri de caractere, dar în cazul nostru nu sunt datele despre care suntem preocupați; trebuie doar să marcăm acel element. Apoi am stabilit incepator la fals. În cele din urmă, dacă aceasta este ultima intrare, o vom marca ca sfârșit.

Dacă sursa imaginii nu este aceeași cu sursa imaginii anterioare, vom reseta prev_item cu sursa imaginii curente; apoi, am stabilit starterul Adevărat. În acest fel, dacă descoperim că următoarea imagine are aceeași sursă ca aceasta, putem marca aceasta ca și starter. În cele din urmă, dacă există o intrare înaintea acesteia și imaginea asociată este ascunsă, știm că intrarea este sfârșitul unei serii (pentru că aceasta are o imagine diferită). Prin urmare, îi vom da un atribut de date care îl marchează ca un sfârșit.

Odată ce am terminat, vom stabili prev_item la nul; o vom reutiliza în curând.

Acum, dacă te uiți la intrările din Firebug, ar trebui să vezi ceva de genul:

Scroll Handler

Acum suntem gata să lucrăm la acel dispozitiv de derulare. Există două părți la această problemă. În primul rând, găsim intrarea cea mai apropiată de partea de sus a paginii. În al doilea rând, facem orice este potrivit pentru imaginea legată de acea intrare.

$ (document) .bind ("defilați", funcția (e) var temp_scroll = win.scrollTop (), jos = ((scroll_top - temp_scroll) < 0) ? true : false, top_item = null, child = null; scroll_top = temp_scroll; // more coming );

Aceasta este shell-ul nostru de scroll; avem câteva variabile pe care le creăm pentru a începe; chiar acum, ia notă de jos. Acest lucru va fi adevărat dacă suntem derulați în jos, falsi dacă suntem derulați. Apoi, noi ne mutăm scroll_top pentru a fi distanța de jos în sus de la care suntem defilați.

Acum, să stabilim top_item pentru a fi intrarea cea mai apropiată în partea de sus a paginii:

top_item = intrări.filter (funcție (i) var distance = $ (this) .offset () top - scroll_top; < (parent_from_top + margin) && distance > (parent_from_top - margine)); );

Acest lucru nu este deloc greu; noi doar folosim filtru metodă pentru a decide care intrare vrem să atribuim top_item. Mai întâi, obținem distanţă scăzând suma pe care am derulat-o de la offsetul de sus al intrării. Atunci vom reveni la adevărat dacă distanţă este între parent_from_top + margine și parent_from_top - margine; altfel, fals. Dacă vă derutează acest lucru, gândiți-vă în acest fel: vrem să revenim la adevărat când un element este chiar în partea de sus a ferestrei; în acest caz, distanţă ar fi egal cu 0. Totuși, trebuie să ținem seama de compensarea de sus a containerului care împachetează "tweet-urile" noastre? așa că ne dorim cu adevărat distanţă la egal parent_from_top.

Dar este posibil ca managerul nostru de parcurgere să nu se declanșeze atunci când suntem exact pe acest pixel, dar în schimb când suntem aproape de el. Am descoperit acest lucru când am înregistrat distanța și am găsit-o ca fiind valori care au fost oprite cu jumătate de pixel; de asemenea, dacă funcția de manipulare a parcurgerii nu este prea eficientă (ceea ce nu va fi, încă nu), este posibil să nu se declanșeze fiecare derulați evenimentul. Pentru a vă asigura că obțineți unul în zona potrivită, adăugăm sau scade o marjă pentru a ne oferi o gamă mică de lucru în interior.

Acum, că avem elementul de top, să facem ceva.

dacă (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 ("vizibilitate", "ascunsă"); anti_repeater.css ('poziție': 'fix', 'sus': img_offset.top + 'px', 'stânga': img_offset.left + "px");  altfel dacă (top_item.attr ("data-8088-ender")) top_item.children (unscrollable.selector) .css ("vizibilitate", "vizibilă"); dacă (anti_repeater) anti_repeater.remove ();  anti_repeater = null;  dacă (! jos) if (top_item.attr ("data-8088-starter")) top_item.children (unscrollable.selector) .css ("vizibilitate"; dacă (anti_repeater) anti_repeater.remove ();  anti_repeater = null;  altfel dacă (top_item.attr ("data-8088-ender")) child = top_item.children (unscrollable.selector); anti_repeater = child.clone () appendTo (document.body); child.css ("vizibilitate", "ascunsă"); anti_repeater.css ('poziție': 'fix', 'sus': img_offset.top + 'px', 'stânga': img_offset.left + "px"); 

Puteți observa că codul de mai sus este aproape același lucru de două ori; amintiți-vă că aceasta este versiunea neoptimizată. Pe măsură ce am parcurs treptat problema, am început să-mi dau seama cum să scot lucrurile; odată ce am rezolvat asta, am lucrat la defilare. Nu a fost evident imediat, până când toată funcționalitatea era în vigoare, că ar fi o asemănare atât de mare. Amintiți-vă, vom optimiza în curând.

Deci, hai să disecăm asta. Dacă elementul de vârf are a Date-8088-starter atributul, atunci, să verificăm dacă anti_repeater a fost stabilit; aceasta este variabila care va indica elementul de imagine care va fi fixat în timp ce pagina se derulează. Dacă anti_repeater nu a fost stabilit, atunci vom primi copilul nostru de top_item intrare care are același selector ca și unscrollable (nu, nu este o modalitate inteligentă de a face acest lucru, o vom îmbunătăți mai târziu). Apoi, clonăm și adăugăm-o corpului. Vom ascunde acel unul și apoi vom poziționa clonatul exact unde ar trebui să meargă.

Dacă elementul nu are a Date-8088-starter atribut, vom verifica pentru Date-8088-Ender atribut. Dacă este acolo, vom găsi copilul potrivit și vom face vizibil, apoi vom elimina anti_repeater și setați acea variabilă la nul.

Din fericire, dacă nu mergem în jos (dacă mergem), este exact invers pentru cele două atribute ale noastre. Și, dacă top_item nu are nici atribute, suntem undeva în mijlocul unei serii și nu trebuie să schimbăm nimic.

Revizuirea performanțelor

Ei bine, acest cod face ceea ce vrem; cu toate acestea, dacă încercați, veți observa că trebuie să derulați foarte incet ca să funcționeze corect. Încercați să adăugați liniile console.profile ( "scroll") și console.profileEnd () ca prima și ultima linie a funcției de manipulare a scroll-ului. Pentru mine, handler-ul durează între 2,5ms - 4ms, având loc 166-170 de apeluri de funcții.

Este prea mult timp ca un robot de derulare să ruleze; și, după cum vă puteți imagina, o conduc pe un computer destul de bine dotat. Observați că unele funcții sunt numite de 30-31 ori; avem 30 de înregistrări cu care lucrăm, deci probabil că aceasta este o parte din șirul celor care le găsesc pe cel superior. Aceasta înseamnă că cu cât mai multe intrări avem, cu atât mai lent acest lucru va rula; atât de ineficient! Deci, trebuie să vedem cum putem îmbunătăți acest lucru.


Pasul 4: JavaScript, Runda 2

Dacă suspectați că jQuery este principalul vinovat aici, aveți dreptate. În timp ce cadre precum jQuery sunt extrem de utile și fac lucrul cu DOM o briză, ei vin cu un compromis: performanță. Da, ei întotdeauna se îmbunătățesc; și da, așa sunt browserele. Cu toate acestea, situația noastră solicită codul cel mai rapid posibil și, în cazul nostru, va trebui să renunțăm la un pic de jQuery pentru o lucrare DOM directă care nu este prea dificilă.

Scroll Handler

Hai cu partea evidentă: ce facem cu top_item odată ce am găsit-o. În prezent, top_item este un obiect jQuery; cu toate acestea, tot ceea ce facem cu jQuery cu top_item este trivial mai greu fără jQuery, așa că o să facem? crude.? Când revedem primirea top_item, ne vom asigura că este un element DOM brut.

Deci, iată ce putem schimba pentru a face mai repede:

  • Putem refactor if-declarațiile noastre pentru a evita cantitatea imensă de duplicare (acesta este mai mult un punct de curățare a codului, nu un punct de performanță).
  • Putem folosi nativul getAttribute metodă, în loc de jQuery attr.
  • Putem obține elementul de la unscrollable care corespunde cu top_item intrare, în loc de utilizare unscrollable.selector.
  • Putem folosi nativul clodeNode și appendChild metode, în loc de versiunile jQuery.
  • Putem folosi stil proprietate în loc de jQuery css metodă.
  • Putem folosi nativul removeNode în loc de jQuery elimina.

Aplicând aceste idei, ajungem la aceasta:

dacă (top_item) if ((down && top_item.getAttribute ("data-8088-starter")) copil = nescrollabil [entries.indexOf (top_item)]; anti_repeater = child.cloneNode (false); document.body.appendChild (anti_repeater); child.style.visibility = "ascuns"; style = anti_repeater.style; style.position = 'fix'; style.top = img_offset.top + 'px'; style.left = img_offset.left + 'px';  dacă (jos && top_item.getAttribute ("data-8088-ender")) || (! down && top_item.getAttribute ("data-8088-starter"))) inscrollable [entries.indexOf (top_item)] .style.visibility = "vizibil"; dacă (anti_repeater) anti_repeater.parentNode.removeChild (anti_repeater);  anti_repeater = null; 

Acesta este un cod mult mai bun: nu numai că scapă de această duplicare, dar nu folosește deloc jQuery. (Pe măsură ce scriu acest lucru, mă gândesc că ar putea fi chiar mai rapid să aplicați o clasă CSS pentru a face stilul, puteți experimenta cu asta!)

S-ar putea să credeți că este la fel de bun ca noi putem obține; cu toate acestea, există o serie de optimizare serioasă care poate avea loc în obținerea de top_item. În prezent, folosim jQuery filtru metodă. Dacă vă gândiți la asta, acest lucru este incredibil de sărac. Știm că numai vom primi un element din această filtrare; cu exceptia filtru funcția nu știe că așa că continuă să ruleze elemente prin funcția noastră după ce am găsit-o pe cea pe care o dorim. Avem 30 de elemente în intrări, așa că este o pierdere de timp destul de mare. Ceea ce vrem să facem este:

pentru (i = 0; intrări [i]; i ++) distance = $ (intrări [i]). dacă (distanța < (parent_from_top + margin) && distance > (parent_from_top - margine)) top_item = intrări [i]; pauză; 

(Alternativ, am putea folosi o buclă în timp, cu condiția !top_item; oricum, nu contează prea mult.)

În acest fel, o dată găsim top_item, putem să nu mai căutăm. Cu toate acestea, putem face mai bine; deoarece derularea este un lucru liniar, putem prezice care element să fie cel mai aproape de topul următor. Dacă mergem în jos, va fi fie același element ca și ultima dată sau cel de după el. Dacă mergem în sus, acesta va fi același element ca și ultima dată sau elementul anterior. Acest lucru va fi util mai departe în jos pe pagina pe care o primim, deoarece bucla pentru întotdeauna începe în partea de sus a paginii.

Deci, cum putem implementa acest lucru? Ei bine, trebuie să începem prin urmărirea a ceea ce top_item a fost în timpul executării anterioare a dispozitivului de derulare a parolei. Putem face acest lucru adăugând

prev_item = top_item;

până la sfârșitul funcției de manipulare a parcurgerii. Acum, acolo unde am găsit înainte top_item, pune aceasta:

dacă (prev_item) prev_item = $ (prev_item); înălțime = înălțime || prev_item.outerHeight (); dacă (în jos && 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) > (marja + parent_from_top)) top_item = intrări [entries.indexOf (prev_item [0]) - 1];  altfel top_item = prev_item [0];  altceva pentru (i = 0; intrări [i]; i ++) distance = $ (intrări [i]). dacă (distanța < (parent_from_top + margin) && distance > (parent_from_top - margine)) top_item = intrări [i]; pauză; 

Aceasta este cu siguranță o îmbunătățire; dacă a fost a prev_item, atunci putem folosi asta pentru a găsi următorul top_item. Matematica aici devine un pic dificilă, dar asta facem noi:

  • Dacă mergem în jos și elementul anterior este complet deasupra părții superioare a paginii, obțineți următorul element (putem folosi indexul prev_item + 1 pentru a găsi elementul potrivit în intrări).
  • Dacă mergem în sus și elementul anterior este destul de departe în jos pe pagină, obțineți intrarea anterioară.
  • În caz contrar, utilizați aceeași intrare ca ultima dată.
  • Dacă nu există top_item, ne folosim pentru buclă ca o rezervă.

Există câteva alte lucruri care trebuie abordate aici. În primul rând, ce e asta? înălţime variabil? Ei bine, dacă toate intrările noastre sunt la aceeași înălțime, putem evita calcularea înălțimii de fiecare dată în cadrul dispozitivului de derulare, făcând-o în configurație. Deci, am adăugat acest lucru la configurație:

height = entries.outerHeight (); dacă (input.length! == intrări.filter (funcție () retur $ (this) .uterHeight () === înălțime;)).) lungime) height = null; 

lui jQuery outerHeight metoda primește înălțimea unui element, inclusiv umplutura și marginile sale. Dacă toate elementele au aceeași înălțime ca prima, atunci înălţime va fi stabilit; in caz contrar înălţime va fi nul. Apoi, când ajungem top_item, putem folosi înălţime dacă este setat.

Celălalt lucru pe care trebuie să-l observați este acesta: ați putea crede că este ineficient să faci prev_item.offset (). top de două ori; nu ar trebui să intre în interiorul unei variabile. De fapt, o vom face o singură dată, deoarece a doua declarație if va fi apelată doar dacă jos este fals; deoarece folosim operatorul logic AND, cele două părți ale celor două declarații dacă niciodată nu vor fi difuzate pe același apel de funcție. Desigur, ați putea pune într-o variabilă dacă credeți că vă păstrează codul mai curat, dar nu face nimic pentru performanță.

Cu toate acestea, încă nu sunt mulțumit. Sigur, managerul nostru de parcurgere este mai rapid, dar putem face mai bine. Folosim doar jQuery pentru două lucruri: pentru a obține outerHeight de prev_item și pentru a obține offset de top de prev_item. Pentru ceva atât de mic, este destul de scump să faci un obiect jQuery. Cu toate acestea, nu există o soluție evidentă, non-jQuery la acestea, așa cum a fost înainte. Deci, hai să ne aruncăm în codul sursă jQuery pentru a vedea exact ce face jQuery; în acest fel, putem scoate biții de care avem nevoie fără să folosim greutatea costisitoare a jQuery însăși.

Să începem cu problema de offset de sus. Iată codul jQuery pentru ofset metodă:

funcție (opțiuni) var elem = acest [0]; dacă (elem ||! elem.ownerDocument) return null;  dacă (opțiuni) return this.each (funcția (i) jQuery.offset.setOffset (aceasta, opțiuni, i););  dacă (elem === elem.ownerDocument.body) returnează jQuery.offset.bodyOffset (elem);  var box = elem.getBoundingClientRect (), doc = elem.ownerDocument, corp = doc.body, docElem = doc.documentElement, clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, top = box.top + (auto.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop) - clientTop, stânga = box.left + (auto.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft; întoarce top: sus, stânga: stânga; 

Acest lucru pare complicat, dar nu este prea rău; avem nevoie doar de offsetul de sus, nu de offsetul stâng, astfel încât să putem scoate acest lucru și să îl folosim pentru a crea funcția proprie:

funcția get_top_offset (elem) var doc = elem.ownerDocument, organism = doc.body, docElem = doc.documentElement; retur elem.getBoundingClientRect () top + (auto.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop) - docElem.clientTop || body.clientTop || 0; 

Tot ce trebuie să facem să treacă în elementul brut DOM, și vom obține compensarea de sus înapoi; Grozav! astfel încât să putem înlocui toate noastre offset (). top apeluri cu această funcție.

Ce zici de outerHeight? Acesta este un pic mai complicat, deoarece folosește una dintre funcțiile jQuery interne multi-utilizare.

funcția (marja) returnați acest [0]? jQuery.css (acest [0], tip, fals, margine? "margine": "frontieră"): null; 

După cum vedem, de fapt acest lucru face doar un apel către css funcția, cu acești parametri: elementul,? înălțimea ?,? frontiera ?, și fals. Iata cum arata:

funcția (element, nume, forță, extra) if (nume === "lățime" || nume === "înălțime") var val, props = cssShow, which = name === "lățime"? cssWidth: cssHeight; funcția getWH () val = name === "lățime"? elem.offsetWidth: elem.offsetHeight; dacă (extra === "frontieră") return;  jQuery.each (care, funcția () if (! extra) val - = parseFloat (jQuery.curCSS (elem, "padding" + this, true)) | "); val + = parseFloat (jQuery.curCSS (elem," border "+ this +" Width " , adevărat)) || 0;);  dacă (elem.offsetWidth! == 0) getWH ();  altceva jQuery.swap (elem, props, getWH);  retur Math.max (0, Math.round (val));  return jQuery.curCSS (elem, nume, forță); 

S-ar putea să parut fără speranță în acest moment, dar merită să urmați logica? deoarece soluția este minunată! Se pare că putem folosi doar un element offsetHeight proprietate; care ne dă exact ceea ce vrem!

Prin urmare, codul nostru arată acum:

dacă (prev_item) height = height || prev_item.offsetHeight; dacă (în jos && 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) > (marja + parent_from_top)) top_item = intrări [entries.indexOf (prev_item) - 1];  altceva top_item = prev_item;  altceva pentru (i = 0; intrări [i]; i ++) distance = get_top_offset (intrări [i]) - scroll_top; dacă (distanța < (parent_from_top + margin) && distance > (parent_from_top - margine)) top_item = intrări [i]; pauză; 

Acum, nimic nu trebuie să fie un obiect jQuery! Și dacă l-ați rupe în jos, ar trebui să fiți mult sub o milisecundă și să aveți doar câteva apeluri de funcții.

Instrumente utile

Înainte de a concluziona, iată un sfat util pentru săparea în interiorul jQuery ca în cazul în care am făcut-o aici: utilizați jQuery Source Viewer al lui James Padolsey. Este un instrument minunat care face incredibil de simplu să sapi în interiorul codului și să vedeți ce se întâmplă fără a fi prea copleșiți. [Notă Sid: Un alt instrument minunat ar trebui să plătiți - jQuery Deconstructer.]


Concluzie: Ce am acoperit

Sper că v-ați bucurat de acest tutorial! S-ar putea să fi învățat cum să creați un comportament destul de curat de defilare, dar acest lucru nu a fost principalul punct. Ar trebui să luați aceste puncte departe de acest tutorial:

  • Faptul că codul dvs. face ceea ce doriți nu înseamnă că nu poate fi mai curat, mai rapid sau mai bun.
  • jQuery - sau oricare ar fi biblioteca dvs. preferată - este doar JavaScript; poate fi și lent; și uneori, este mai bine să nu-l folosești.
  • Singurul mod în care vă veți îmbunătăți ca dezvoltator JavaScript este dacă vă scoateți din zona dvs. de confort.

Aveți întrebări? Sau poate aveți o idee despre cum să îmbunătățiți acest lucru chiar mai mult! Hai să discutăm în comentarii!

Cod