Caruselile sunt o bază de site-uri de streaming și de e-commerce. Atât Amazon cât și Netflix le folosesc ca instrumente de navigare proeminente. În acest tutorial, vom evalua designul interacțiunii ambelor și vom folosi constatările noastre pentru implementarea caruselului perfect.
În această serie de tutori, vom învăța, de asemenea, câteva funcții ale Popmotion, un motor de mișcare JavaScript. Acesta oferă instrumente de animație cum ar fi tweens (utile pentru paginare), urmărirea pointerului (pentru derulare) și fizica de primăvară (pentru deliciile noastre de finisare.)
Partea 1 va evalua modul în care Amazon și Netflix au implementat defilare. Apoi vom implementa un carusel care poate fi derulat prin atingere.
Până la sfârșitul acestei serii, vom fi implementat parcurgerea roților și touchpad-ului, paginarea, barele de progres, navigarea de la tastatură și câteva mici atingeri utilizând fizica primăvară. De asemenea, vom fi expuși unor compoziții funcționale de bază.
Ce este necesar ca un carusel să fie "perfect"? Trebuie să fie accesibil prin:
În cele din urmă, vom face lucruri care să avanseze în continuare și să facă o bucată de UX încrezătoare și încântătoare, făcând caruselul să răspundă clar și visceral cu fizica primăvară atunci când cursorul a atins sfârșitul.
Mai întâi, hai să obținem HTML și CSS necesare pentru a construi un carusel rudimentar prin forfecarea acestui CodePen.
Penul este configurat cu Sass pentru preprocesarea CSS și Babel pentru transpunerea JavaScript ES6. Am inclus, de asemenea, Popmotion, cu care pot fi accesate window.popmotion
.
Puteți copia codul într-un proiect local, dacă preferați, dar va trebui să vă asigurați că mediul dvs. suportă Sass și ES6. De asemenea, va trebui să instalați Popmotion cu npm install popmotion
.
Pe o anumită pagină, s-ar putea să avem multe caruseluri. Așadar, avem nevoie de o metodă de încapsulare a stării și funcționalității fiecăruia.
O să folosesc a funcția din fabrică mai degrabă decât a clasă
. Funcțiile din fabrică evită nevoia de a folosi confuzia frecventă acest
și va simplifica codul pentru scopurile acestui tutorial.
În editorul JavaScript, adăugați această funcție simplă:
carusel funcțional (container) carusel (document.querySelector ("container"));
Vom adăuga codul nostru carusel specific în interiorul lui carusel
funcţie.
Prima noastră sarcină este să facem parcurgerea caruselului. Există două modalități prin care putem face acest lucru:
Soluția evidentă ar fi stabilirea overflow-x: derulați
pe cursor. Acest lucru ar permite derularea nativă pe toate browserele, inclusiv dispozitivele cu atingere și orizontale ale mouse-ului.
Există, totuși, dezavantaje ale acestei abordări:
Alternativ:
translateX
Am putea anima și caruselul translateX
proprietate. Acest lucru ar fi foarte versatil, deoarece vom putea să implementăm exact designul pe care îl dorim. translateX
este, de asemenea, foarte performant, spre deosebire de CSS stânga
proprietatea poate fi manipulată de GPU-ul dispozitivului.
În partea inferioară, va trebui să reimplementăm funcția defilare folosind JavaScript. E mai multă muncă, mai mult cod.
Ambele caruseluri Amazon și Netflix fac diferite compromisuri în abordarea acestei probleme.
Amazon animă caruselul stânga
proprietate în modul "desktop". însuflețitor stânga
este o alegere incredibil de săracă, deoarece schimbarea acesteia declanșează o recalculare a aspectului. Acest lucru este CPU-intensiv, iar mașinile mai vechi se vor lupta pentru a atinge 60fps.
Oricine a luat decizia de a anima stânga
in loc de translateX
trebuie să fie un adevărat idiot (spoiler: am fost eu, înapoi în 2012. Nu am fost la fel de iluminat în acele zile.)
Când detectează un dispozitiv tactil, caruselul folosește derularea nativă a browserului. Problema care se activează numai în modul "mobil" este utilizarea utilizatorilor desktop cu roți orizontale de defilare. De asemenea, înseamnă că orice conținut în afara caruselului va trebui tăiat vizual:
Netflix animă în mod corect caruselul translateX
de proprietate, și face acest lucru pe toate dispozitivele. Acest lucru le permite să aibă un design care sângerează în afara caruselului:
Aceasta, la rândul său, le permite să realizeze un design fantezist în care elementele sunt lărgite în afara marginilor x și y ale caruselului și obiectele din jur se deplasează din calea lor:
Din nefericire, reimplementarea de către Netflix a derulării pentru dispozitive touch este nesatisfăcătoare: folosește un sistem de paginare bazat pe gesturi, care se simte încet și greoi. De asemenea, nu există nici o considerație pentru roțile orizontale de defilare.
Putem face mai bine. Să codificăm!
Prima noastră mișcare este de a apuca .cursor
nodul. În timp ce suntem la ea, să luăm elementele pe care le conține, astfel încât să putem înțelege dimensiunea glisorului.
carusel de funcții (container) const slider = container.querySelector ("slider"); const elemente = slider.querySelectorAll ('. item');
Putem realiza zona vizibilă a cursorului prin măsurarea lățimii sale:
const sliderVisibleWidth = slider.offsetWidth;
Vom dori, de asemenea, lățimea totală a tuturor articolelor conținute în. Să ne păstrăm carusel
funcția relativ curată, să punem acest calcul într-o funcție separată în partea de sus a fișierului nostru.
Prin utilizarea getBoundingClientRect
pentru a măsura stânga
offset de primul nostru element și dreapta
offset de ultimul nostru element, putem folosi diferența dintre ele pentru a găsi lățimea totală a tuturor elementelor.
funcția getTotalItemsWidth (elemente) const left = elemente [0] .getBoundingClientRect (); const right = elemente [items.length - 1] .getBoundingClientRect (); întoarcere dreapta - stânga;
După noi sliderVisibleWidth
măsurare, scrieți:
const totalItemsWidth = getTotalItemsWidth (elemente);
Acum ne putem da seama de distanța maximă la care caruselul nostru ar trebui să aibă dreptul să parcurgă. Este lățimea totală a tuturor articolelor noastre, minus o lățime întreagă a glisorului nostru vizibil. Acesta oferă un număr care permite elementului din dreapta să se alinieze cu dreptul glisorului nostru:
const maxXOffset = 0; const minXOffset = - (totalItemsWidth - cursorVisibleWidth);
Cu ajutorul acestor măsurători, suntem gata să începem să derulăm caruselul nostru.
translateX
Popmotion vine cu un renderer CSS pentru setarea simplă și performantă a proprietăților CSS. De asemenea, vine cu o funcție de valoare care poate fi utilizată pentru a urmări numerele și, important (așa cum vom vedea în curând), să interogăm viteza lor.
În partea de sus a fișierului JavaScript, importați-le astfel:
const css, value = fereastra.popmotion;
Apoi, pe linie după ce am stabilit minXOffset
, creați un renderer CSS pentru glisorul nostru:
const sliderRenderer = css (cursor);
Și creați o valoare
pentru a urmări offsetul culisorului nostru x și actualizați glisorul translateX
proprietate când se schimbă:
const sliderX = valoare (0, (x) => sliderRenderer.set ('x', x));
Acum, deplasarea cursorului pe orizontală este la fel de simplă:
sliderX.set (-100);
Incearca-l!
Vrem ca caruselul nostru să înceapă derularea atunci când a utilizatortrage glisorul orizontal și pentru a opri derularea când un utilizator nu mai atinge ecranul. Operatorii noștri de evenimente vor arăta astfel:
permiteți acțiune; funcția stopTouchScroll () document.removeEventListener ("atingere", stopTouchScroll); funcția startTouchScroll (e) document.addEventListener ('touchend', stopTouchScroll); slider.addEventListener ("touchstart", startTouchScroll, pasiv: false);
În a noastră startTouchScroll
, dorim sa:
sliderX
.touchmove
pentru a vedea dacă utilizatorul trage vertical sau orizontal.După document.addEventListener
, adăuga:
dacă (acțiune) action.stop ();
Acest lucru va opri orice alte acțiuni (cum ar fi parcurgerea momentei pe care o vom implementa în fizică stopTouchScroll
) de la deplasarea cursorului. Acest lucru va permite utilizatorului să "captureze" glisorul imediat dacă trece printr-un element sau titlu pe care dorește să facă clic.
Apoi, trebuie să stocăm punctul de contact de origine. Acest lucru ne va permite să vedem în ce direcție utilizatorul își mișcă degetul în continuare. Dacă este o mișcare verticală, vom permite derularea paginii ca de obicei. Dacă este o mișcare orizontală, vom deplasa în schimb sliderul.
Vrem să împărtășim asta touchOrigin
între operatorii de evenimente. Deci dupa permiteți acțiune;
adăuga:
permiteți touchOrigin = ;
Înapoi în nostru startTouchScroll
handler, adăugați:
const ating = e.touches [0]; touchOrigin = x: touch.pageX, y: touch.pageY;
Acum putem adăuga a touchmove
ascultător de evenimente la document
pentru a determina direcția de tragere bazată pe aceasta touchOrigin
:
document.addEventListener ('touchmove', determineDragDirection);
Al nostru determineDragDirection
funcția va măsura următoarea locație a atingerii, va verifica dacă a fost mutată și, dacă este cazul, va măsura unghiul pentru a determina dacă este verticală sau orizontală:
funcția defineDragDirection (e) const touch = e.changedTouches [0]; const touchLocation = x: touch.pageX, y: touch.pageY;
Popmotion include câteva calculatoare utile pentru măsurarea distanțelor dintre două coordonate x / y. Putem importa astfel:
const calc, css, valoare = window.popmotion;
Apoi, măsurarea distanței dintre cele două puncte este o chestiune de utilizare a distanţă
calculator:
const dist = calc.distance (touchOrigin, touchLocation);
Acum, dacă atingerea sa mișcat, putem dezactiva acest ascultător al evenimentului.
dacă (! distanța) retur; document.removeEventListener ('touchmove', determineDragDirection);
Măsurați unghiul dintre cele două puncte cu unghi
calculator:
unghiul const = calc.angle (touchOrigin, touchLocation);
Putem folosi aceasta pentru a determina dacă acest unghi este un unghi orizontal sau vertical, prin trecerea acestuia la următoarea funcție. Adăugați această funcție la începutul fișierului nostru:
funcția angleIsVertical (unghiul) const esteUp = (unghiul <= -90 + 45 && angle >= -90 - 45); const esteDown = (unghiul <= 90 + 45 && angle >= 90 - 45); retur (isUp || isDown);
Această funcție revine Adevărat
dacă unghiul prevăzut este în intervalul -90 +/- 45 grade (drept în sus) sau 90 +/- 45 de grade (drept în jos.) Deci, putem adăuga un alt întoarcere
dacă această funcție revine Adevărat
.
dacă (return angleUsVertical (unghi));
Acum știm că utilizatorul încearcă să deruleze caruselul, putem începe să urmărim degetul. Popmotion oferă o acțiune indicatoare care va afișa coordonatele x / y ale mouse-ului sau indicatorul de atingere.
Mai întâi, importă ac indicator
:
const calc, css, pointer, valoare = window.popmotion;
Pentru a urmări intrarea touch, furnizați evenimentul inițial ac indicator
:
action = pointer (e) .start ();
Vrem să măsuram inițial X
poziționați indicatorul nostru și aplicați orice mișcare spre cursor. Pentru asta, putem folosi un transformator numit applyOffset
.
Transformatoarele sunt funcții pure care iau o valoare și o returnează - da-transformată. De exemplu: const dublu = (v) => v * 2
.
const calc, css, pointer, transform, valoare = window.popmotion; const applyOffset = transformă;
applyOffset
este o funcție curried. Aceasta înseamnă că atunci când o numim, ea creează o nouă funcție care poate fi apoi trecută printr-o valoare. Mai întâi o numim cu un număr pe care dorim să îl măsuram, în acest caz valoarea curentă action.x
, și un număr pentru a aplica acea compensare. În acest caz, asta e al nostru sliderX
.
Deci, noi applyOffset
funcția va arăta astfel:
const applyPointerMovement = applyOffset (action.x.get (), sliderX.get ());
Acum putem folosi această funcție în pointer producție
apel invers pentru a aplica cursorul mișcării către cursor.
action.output ((x) => slider.set (applyPointerMovement (x)));
Caruselul este acum trasabil prin atingere! Puteți testa acest lucru utilizând emularea de dispozitive în Instrumentele de dezvoltare ale Chrome.
Se simte putin janky, nu? S-ar putea să fi întâmpinat defilare care se simte așa: Îți ridici degetul și scrollingul se oprește. Sau scrollingul se oprește și apoi o mică animație preia falsul o continuare a derulării.
Nu o să facem asta. Putem folosi acțiunea fizică din Popmotion pentru a lua viteza adevărată sliderX
și aplicați frecare acestuia pe o perioadă de timp.
În primul rând, adăugați-l la lista noastră tot mai mare de importuri:
const calc, css, fizică, pointer, valoare = window.popmotion;
Apoi, la sfârșitul nostru stopTouchScroll
adăugați:
dacă (acțiune) action.stop (); actiune = fizica (din: sliderX.get (), viteza: sliderX.getVelocity (), frecare: 0.2) .output ((v) => sliderX.set (v)).
Aici, din
și viteză
sunt setate cu valoarea curentă și viteza de sliderX
. Acest lucru ne asigură că simularea fizică are aceleași condiții inițiale ca și mișcarea de tragere a utilizatorului.
frecare
este setat ca 0.2
. Fricțiunea este setată ca o valoare de la 0
la 1
, cu 0
fără să fie deloc fricțiune și 1
fiind frecare absolută. Încercați să jucați cu această valoare pentru a vedea schimbarea pe care o face la "sentimentul" caruselului atunci când un utilizator nu mai trage.
Numerele mai mici vor face să se simtă mai ușoare, iar numerele mai mari vor face mișcarea mai grea. Pentru o mișcare de defilare, simt 0.2
lovește un echilibru frumos între erratic și lent.
Dar există o problemă! Dacă ați jucat cu noul carusel tactil, este evident. Nu am limitat mișcarea, făcând posibilă aruncarea literală a caruselului!
Există un alt transformator pentru această slujbă, clemă
. Aceasta este, de asemenea, o funcție curried, adică dacă o numim cu o valoare min și max, să spunem 0
și 1
, aceasta va reveni la o nouă funcție. În acest exemplu, noua funcție va restricționa orice număr dat între el 0
și 1
:
clemă (0, 1) (5); // returnează 1
Mai întâi, importă clemă
:
const applyOffset, clamp = transformare;
Vrem să folosim această funcție de strângere pe caruselul nostru, deci adăugați această linie după ce definim minXOffset
:
const clampXOffset = clemă (minXOffset, maxXOffset);
Vom modifica cele două producție
am stabilit acțiunile noastre folosind o compoziție ușor funcțională cu țeavă
transformator.
Când numim o funcție, scriem astfel:
foo (0);
Dacă vrem să dăm rezultatul acelei funcții unei alte funcții, am putea scrie așa:
bar (foo (0));
Acest lucru devine ușor de citit și se agravează doar pe măsură ce adăugăm tot mai multe funcții.
Cu țeavă
, putem compune o nouă funcție din foo
și bar
pe care le putem reutiliza:
const foobar = țeava (foo, bar); foobar (0);
Este, de asemenea, scris într-un început natural -> ordine de terminare, ceea ce face mai ușor de urmat. Putem folosi asta pentru a compune applyOffset
și clemă
într-o singură funcție. Import țeavă
:
const applyOffset, clemă, țeavă = transformă;
Inlocuieste producție
apel invers al nostru ac indicator
cu:
(x) => x, aplicaOffset (action.x.get (), sliderX.get ()), clampXOffset, (v) => sliderX.set (v))
Și înlocuiți producție
apel invers fizică
cu:
țeavă (clampXOffset, (v) => sliderX.set (v))
Acest tip de compoziție funcțională poate crea cu ușurință procese descriptive, pas cu pas, din funcții mai mici, reutilizabile.
Acum, când trageți și aruncați caruselul, nu se va mișca în afara limitelor sale.
Oprirea bruscă nu este foarte satisfăcătoare. Dar aceasta este o problemă pentru o parte ulterioară!
Asta-i totul pentru partea 1. Până acum, am analizat carusele existente pentru a vedea punctele forte și punctele slabe ale diferitelor abordări de derulare. Am folosit metoda de urmărire a datelor și fizică de la Popmotion pentru a anima performanțele caruselului nostru translateX
cu deplasare tactilă. Am fost, de asemenea, introduși în compoziția funcțională și funcțiile curried.
Puteți lua o versiune comentată a "povestii de până acum" pe acest CodePen.
În următoarele rate, vom analiza:
Așteaptă cu nerăbdare să te văd acolo!