Creați Caruselul Perfect, Partea 2

Bine ați revenit la seria tutorial Perfect Carousel. Facem un carusel accesibil și încântător, folosind funcțiile JavaScript și Popmotion pentru fizică, tween și de urmărire a intrărilor.

În partea 1 a tutorialului nostru, am aruncat o privire asupra modului în care Amazon și Netflix și-au creat carusele și au evaluat argumentele pro și contra ale abordărilor lor. Odată cu învățarea noastră, am decis o strategie pentru caruselul nostru și o aplicație de derulare prin atingere cu ajutorul fizicii.

În partea a 2-a, vom implementa parcurgerea mouse-ului orizontal. De asemenea, ne vom uita la unele tehnici comune de paginare și vom implementa una. În cele din urmă, vom conecta o bară de progres care va indica cât de departe prin carusel este utilizatorul.

Puteți restabili punctul de salvare prin deschiderea acestui CodPen, care preia locul unde am rămas.

Orizontal Mouse Scroll

Este rar ca un carusel JavaScript să respecte defilarea orizontală a mouse-ului. Este o rușine: pe laptopuri și șoareci care implementează derularea orizontală pe bază de impuls, aceasta este de departe cea mai rapidă modalitate de a naviga pe carusel. Este la fel de rău ca și forțarea utilizatorilor touch pentru a naviga prin butoane, mai degrabă decât să glisați.

Din fericire, acesta poate fi implementat în doar câteva rânduri de cod. La sfârșitul tău carusel adăugați un nou ascultător al evenimentului:

container.addEventListener ("roată", pe roată);

Sub tine startTouchScroll eveniment, adăugați o funcție stub numită onWheel:

funcția peTraw (e) console.log (e.deltaX)

Acum, dacă rulați rotița de derulare peste carusel și verificați panoul consolei, veți vedea distanța roții la ieșirea axei x.

La fel ca la atingere, dacă mișcarea roții este în mare parte verticală, pagina ar trebui să parcurgă ca de obicei. Dacă este orizontală, vrem să capturăm mișcarea roții și să o aplicăm pe carusel. Deci, în onWheel, inlocuieste console.log cu:

unghiul const = calc.angle (x: e, deltaX, y: e, deltaY); dacă (return angleUsVertical (unghi)); e.stopPropagation (); e.preventDefault ();

Acest bloc de cod va opri derularea paginii dacă parcurgerea este orizontală. Actualizarea dispunerii x a cursorului este acum doar o problemă de a lua evenimentul deltaX proprietate și adăugând că la curentul nostru sliderX valoare:

const newX = clampXOffset (sliderX.get () + - e.deltaX); sliderX.set (newX);

Reutilizăm anterioarele noastre clampXOffset funcția de a încheia acest calcul și asigurați-vă că caruselul nu derulează dincolo de limitele sale măsurate.

O parte din evenimentele Scroll Scroll

Orice tutorial bun care se ocupă de evenimentele de intrare va explica cât de important este să accelerați aceste evenimente. Acest lucru se datorează faptului că evenimentele derulării, mouse-ului și atingerii pot să apară mai repede decât rata de cadre a dispozitivului.

Nu doriți să efectuați o muncă inutilă de resurse, cum ar fi redarea caruselului de două ori într-un singur cadru, deoarece este o risipă de resurse și o modalitate rapidă de a face o interfață lentă.

Acest tutorial nu a atins acest lucru, deoarece randatorii furnizați de Popmotion implementează programul Framesync, un planificator de locuri de muncă sincronizat cu un cadru mic. Asta înseamnă că ai putea suna (v) => sliderRenderer.set ("x", v) de mai multe ori la rând, iar redarea costisitoare se va întâmpla doar o singură dată, în cadrul următor.

Paginare

Aia e terminată. Acum trebuie să injectăm o viață în butoanele de navigație care nu au fost date până acum.

Acum, acest tutorial este despre interacțiune, deci nu ezitați să proiectați aceste butoane după cum doriți. Personal, găsesc săgețile direcționale mai intuitive (și pe deplin internaționalizate implicit!).

Cum ar trebui să funcționeze paginarea?

Există două strategii clare pe care le putem lua atunci când paginam caruselul: element cu element sau primul obiect ascuns. Există o singură strategie corectă, dar, pentru că l-am văzut pe celălalt implementat atât de des, am crezut că merită explicat De ce este incorect.

1. Articol după articol

Trebuie doar să măsurați decalajul x al următorului element din listă și să animați raftul cu această sumă. Este un algoritm foarte simplu pe care il presupun ca este ales pentru simplitatea sa, mai degraba decat pentru user-friendliness.

Problema este că majoritatea ecranelor vor putea să afișeze o mulțime de articole la un moment dat, iar oamenii le vor scana pe toate înainte de a încerca să navigheze.

Se simte lent, dacă nu chiar frustrant. Singura situație în care aceasta ar fi o alegere bună este dacă tu ști elementele din carusel sunt de aceeași lățime sau doar puțin mai mici decât zona vizibilă.

Cu toate acestea, dacă ne uităm la mai multe elemente, este mai bine să folosim prima metodă a elementului neclar:

2. Primul element obscur

Această metodă caută pur și simplu primul obiect ascuns în direcția în care vrem să mutăm caruselul, ia-o x offset, și apoi merge la asta.

În acest fel, tragem numărul maxim de elemente noi care se bazează pe presupunerea că utilizatorul a văzut pe toți cei prezenți în prezent.

Deoarece noi tragem mai multe elemente, caruselul necesită mai puține clicuri pentru a naviga în jur. Navigarea mai rapidă va spori angajamentul și va asigura utilizatorilor să vadă mai multe dintre produsele dvs..

Ascultători de evenimente

În primul rând, să organizăm ascultătorii evenimentului, astfel încât să putem începe să jucăm cu paginarea.

Mai întâi trebuie să selectăm butoanele noastre anterioare și viitoare. La top din carusel adăugați:

const nextButton = container.querySelector ('next'); const preButton = container.querySelector ('. prev');

Apoi, la fund din carusel funcția, adăugați ascultătorii evenimentului:

nextButton.addEventListener ("clic", gotoNext); prevButton.addEventListener ("clic", gotoPrev);

În cele din urmă, chiar deasupra blocului dvs. de ascultători de evenimente, adăugați funcțiile reale:

funcția goto (delta)  const gotoNext = () => goto (1); const gotoPrev = () => goto (-1);

mergi la este funcția care se va ocupa de toate logica pentru paginare. Este pur și simplu un număr care reprezintă direcția de deplasare pe care dorim să o paginăm. gotoNext și gotoPrev numiți pur și simplu această funcție 1 sau -1, respectiv.

Calculul unei pagini

Un utilizator poate derula liber acest carusel, și există n obiecte din interiorul acestuia și caruselul poate fi redimensionat. Deci, conceptul de pagină tradițională nu este direct aplicabil aici. Nu vom număra numărul de pagini.

În schimb, atunci când mergi la funcția se numește, vom privi în direcția deltă și găsiți primul element parțial acoperit. Acesta va deveni primul element de pe următoarea "pagină".

Primul pas este să obțineți offsetul curent x al cursorului nostru și să îl folosim cu lățimea vizibilă completă a cursorului pentru a calcula o compensare "ideală" la care ne-am dori să defilați. Deplasarea ideală este ceea ce noi ar defilați la dacă suntem naivi la conținutul cursorului. Acesta oferă un loc frumos pentru ca noi să începem să căutăm primul nostru articol.

const curentX = sliderX.get (); permiteți targetX = currentX + (- sliderVisibleWidth * delta);

Putem folosi o optimizare optimă aici. Prin oferirea noastră targetX la clampXOffset funcția pe care am făcut-o în tutorialul anterior, putem vedea dacă rezultatul său este diferit față de targetX. Dacă este, înseamnă că noi targetX este în afara limitelor noastre scrollabile, deci nu trebuie să dăm seama de cel mai apropiat element. Noi doar defilați până la capăt.

const clampedX = clampXOffset (targetX); targetX = (targetX === clampedX)? findClosestItemOffset (targetX, delta): clampedX;

Găsirea celui mai apropiat element

Este important să rețineți că următorul cod lucrează la presupunerea că toate articolele din carusel sunt de aceeași mărime. Sub această ipoteză, putem face optimizări, cum ar fi nu trebuie să măsuram dimensiunea fiecărui element. Dacă articolele dvs. sunteți de dimensiuni diferite, acest lucru va fi încă un bun punct de plecare. 

Deasupra ta mergi la adăugați funcția findClosestItemOffset funcție menționată în ultimul fragment:

funcția findClosestItem (targetX, delta) 

În primul rând, trebuie să știm cât de largi sunt elementele noastre și distanța dintre ele. Element.getBoundingClientRect () metoda poate furniza toate informațiile de care avem nevoie. Pentru lăţime, pur și simplu măsuram primul element al elementului. Pentru a calcula spațiul dintre elemente, putem măsura dreapta compensarea primului element și a stânga compensarea celui de-al doilea și apoi scăderea celui din urmă: 

const dreapta, lățime = elemente [0] .getBoundingClientRect (); const spacing = elemente [1] .getBoundingClientRect () stânga-dreapta;

Acum, cu targetX și deltă variabilele pe care le-am trecut prin funcție, avem toate datele de care avem nevoie pentru a calcula rapid o compensare pentru a defila la.

Calculul este de a împărți valoarea absolută targetX valoare de către lățime + spațiere. Acest lucru ne va da numărul exact de articole pe care le putem plasa în interiorul acelei distanțe.

const totalItems = Math.abs (țintăX) / (lățime + spațiere);

Apoi, rotunjiți în sus sau în jos, în funcție de direcția de paginare deltă). Acest lucru ne va da numărul de complet elemente pe care le putem încadra.

const totalCompleteItems = delta === 1? Math.floor (totalItems): Math.ceil (totalItems);

În cele din urmă, multiplicați numărul prin lățime + spațiere pentru a ne oferi o culoare de compensare cu un element complet.

return 0 - totalCompleteItems * (lățime + spațiere);

Animați paginarea

Acum că avem pe noi targetX calculate, putem anima la ea! Pentru aceasta, vom folosi motorul de lucru al animatiei web Tween.

Pentru cei neinițiați, "tween" este scurt pentru fiTween. O schimbare variază de la o valoare la alta, pe o anumită durată de timp. Dacă ați utilizat tranzițiile CSS, acesta este același lucru. 

Există o serie de beneficii (și deficiențe!) De a folosi JavaScript peste CSS pentru tweens. În acest caz, pentru că animăm și noi sliderX cu ajutorul fizicii și al utilizatorului, va fi mai ușor să rămânem în acest flux de lucru pentru tween.

De asemenea, înseamnă că mai târziu putem conecta un bar de progres și va funcționa în mod natural cu toate animațiile noastre, gratuit.

Vrem mai întâi să importăm Tween de la Popmotion:

const calc, css, relaxare, fizică, pointer, transformare, tween, valoare = window.popmotion;

La sfârșitul anului mergi la funcția, putem adăuga tween-ul nostru care animă currentX la targetX:

tween (de la: curentX, la: targetX, onUpdate: sliderX) start ();

În mod prestabilit, Popmotion setează durată la 300 milisecunde și uşura la easing.easeOut. Acestea au fost selectate în mod specific pentru a oferi o simțire receptivă animațiilor care răspund la intrarea utilizatorilor, dar simțiți-vă liber să jucați și să vedeți dacă veniți cu ceva care să se potrivească mai bine simțului brandului dvs..

Indicator de progres

Este util pentru utilizatori să aibă indicii despre locul unde se află în carusel. Pentru aceasta, putem conecta un indicator de progres.

Bara de progres a dvs. poate fi desenată în mai multe moduri. Pentru acest tutorial, am făcut un div de culoare, de 5px, care rulează între butoanele anterioare și următoare. Este modul în care ne-am conectat la codul nostru și animăm bara care este importantă și este în centrul acestui tutorial.

Nu ați văzut încă indicatorul, deoarece noi l-am creat inițial transforma: scaleX (0). Noi folosim a scară transformați pentru a regla lățimea barei, deoarece, așa cum am explicat în partea 1, transformările sunt mai performante decât schimbarea proprietăților stânga sau, în acest caz, lăţime.

De asemenea, ne permite să scriem cu ușurință cod care stabilește scara ca a procent: valoarea curentă a sliderX între minXOffset și maxXOffset.

Să începem prin selectarea noastră div.progress-bar după noi previousButton selector:

const progressBar = container.querySelector (". progress-bar");

După ce definim sliderRenderer, putem adăuga un renderer pentru bara de progres:

const progresBarRenderer = css (progressBar);

Acum, să definim o funcție pentru actualizarea scaleX din bara de progres.

Vom folosi a Calc funcția numită getProgressFromValue. Acest lucru are nevoie de o gamă largă, în cazul nostru min și maxXOffset, și un al treilea număr. Se întoarce progres, un număr între 0 și 1, a celui de-al treilea număr din intervalul dat.

actualizare funcțieProgressBar (x) const progres = calc.getProgressFromValue (maxXOffset, minXOffset, x); progressBarRenderer.set ("scaleX", progres); 

Am scris gama aici ca maxXOffset, minXOffset când, intuitiv, ar trebui inversată. Asta pentru ca X este o valoare negativă și maxXOffset este, de asemenea, o valoare negativă în timp ce minXOffset este 0.  0 este din punct de vedere tehnic cea mai mare dintre cele două numere, dar valoarea mai mică reprezintă de fapt decalajul maxim. Negative, huh?

Vrem ca indicatorul de progres să se actualizeze în regim de blocare cu sliderX, așa că haideți să modificăm această linie:

const sliderX = valoare (0, (x) => sliderRenderer.set ('x', x));

În această linie:

const sliderX = valoare (0, (x) => updateProgressBar (x); sliderRenderer.set ('x', x););

Acum, oricând sliderX actualizări, la fel și bara de progres.

Concluzie

Asta e pentru această tranșă! Puteți obține codul cel mai recent pe acest CodePen. Am introdus cu succes derularea rotilor pe orizontală, paginarea și o bară de progres.

Caruselul este într-o formă destul de bună până acum! În ultima tranșă, o vom face un pas mai departe. Vom face caruselul complet accesibil pentru a vă asigura că oricine îl poate folosi. 

Vom adăuga, de asemenea, câteva cupluri minunate folosind remorcherul alimentat de primăvară atunci când un utilizator încearcă să deruleze caruselul dincolo de limitele sale utilizând fie o defilare touch, fie o paginare. 

Ne vedem atunci!

Cod