I caroselli sono un punto fermo di streaming e siti di e-commerce. Sia Amazon che Netflix li usano come strumenti di navigazione di primo piano. In questo tutorial, valuteremo la progettazione dell'interazione di entrambi e useremo i risultati per implementare la giostra perfetta.
In questa serie di tutorial, impareremo anche alcune funzioni di Popmotion, un motore di animazione JavaScript. Offre strumenti di animazione come le interpolazioni (utili per l'impaginazione), il puntatore (per lo scorrimento) e la fisica primaverile (per i nostri deliziosi tocchi finali.)
La prima parte valuterà come Amazon e Netflix hanno implementato lo scrolling. Implementeremo quindi un carosello che può essere scostato via touch.
Entro la fine di questa serie, avremo implementato scroll, touchpad, impaginazioni, barre di avanzamento, navigazione da tastiera e alcuni piccoli tocchi utilizzando la fisica delle molle. Saremo anche stati esposti ad alcune composizioni funzionali di base.
Cosa ci vuole per una giostra per essere "perfetta"? Deve essere accessibile da:
Infine, faremo un ulteriore passo in più e rendere questo un pezzo di UX fiducioso e piacevole facendo sì che la giostra risponda in modo chiaro e viscerale alla fisica della molla quando il cursore ha raggiunto la fine.
Per prima cosa, prendiamo l'HTML e il CSS necessari per costruire un carosello rudimentale mediante la forchetta di questo CodePen.
La penna è configurata con Sass per la preelaborazione di CSS e Babel per la trascrizione di ES6 JavaScript. Ho anche incluso Popmotion, a cui si può accedere window.popmotion
.
È possibile copiare il codice in un progetto locale se si preferisce, ma è necessario assicurarsi che il proprio ambiente supporti Sass ed ES6. Devi anche installare Popmotion con npm installa popmotion
.
In qualsiasi pagina, potremmo avere molti caroselli. Quindi abbiamo bisogno di un metodo per incapsulare lo stato e la funzionalità di ciascuno.
Userò a funzione di fabbrica piuttosto che a classe
. Le funzioni di fabbrica evitano la necessità di usare la confusione spesso Questo
parola chiave e semplificherà il codice ai fini di questo tutorial.
Nel tuo editor JavaScript, aggiungi questa semplice funzione:
function carousel (container) carousel (document.querySelector ('. container'));
Inseriremo il nostro codice specifico del carosello all'interno di questo giostra
funzione.
Il nostro primo compito è quello di far scorrere la giostra. Ci sono due modi in cui possiamo fare questo:
La soluzione ovvia sarebbe quella di impostare overflow-x: scroll
sul cursore. Ciò consentirebbe lo scorrimento nativo su tutti i browser, inclusi i dispositivi touch e mouse wheel orizzontali.
Ci sono, tuttavia, degli svantaggi di questo approccio:
In alternativa:
translateX
Potremmo anche animare il carosello translateX
proprietà. Questo sarebbe molto versatile in quanto saremmo in grado di implementare esattamente il design che ci piace. translateX
è anche molto performante, a differenza del CSS sinistra
proprietà che può essere gestita dalla GPU del dispositivo.
Sul lato negativo, dovremmo reimplementare la funzionalità di scorrimento utilizzando JavaScript. Questo è più lavoro, più codice.
Sia i caroselli Amazon che Netflix fanno diversi compromessi nell'affrontare questo problema.
Amazon anima il carosello sinistra
proprietà in modalità "desktop". Animazione sinistra
è una scelta incredibilmente scarsa, in quanto la modifica attiva un ricalcolo del layout. Si tratta di un utilizzo intensivo della CPU, e le macchine più vecchie faranno fatica a raggiungere 60 fps.
Chiunque abbia preso la decisione di animare sinistra
invece di translateX
deve essere un vero idiota (spoiler: ero io, nel 2012. Non eravamo così illuminati in quei giorni).
Quando rileva un dispositivo touch, il carousel utilizza lo scorrimento nativo del browser. Il problema con la sola abilitazione di questo in modalità "mobile" è l'assenza di utenti desktop con rotelle di scorrimento orizzontali. Significa anche che qualsiasi contenuto al di fuori del carosello dovrà essere visivamente troncato:
Netflix anima correttamente il carosello translateX
proprietà, e lo fa su tutti i dispositivi. Ciò consente loro di avere un design che sanguina al di fuori del carosello:
Questo, a sua volta, consente loro di creare un disegno di fantasia in cui gli oggetti vengono ingranditi al di fuori dei bordi xey del carosello e gli oggetti circostanti si spostano dal loro modo:
Sfortunatamente, la reimplementazione di Netflix dello scrolling dei dispositivi touch non è soddisfacente: utilizza un sistema di impaginazione basato sui gesti che sembra lento e macchinoso. Non c'è nemmeno considerazione per le ruote di scorrimento orizzontali.
Possiamo fare di meglio Facciamo codice!
La nostra prima mossa è afferrare il .cursore
nodo. Mentre ci siamo, prendiamo gli oggetti in esso contenuti in modo che possiamo capire la dimensione del cursore.
function carousel (container) const slider = container.querySelector ('. slider'); const items = slider.querySelectorAll ('. item');
Possiamo calcolare l'area visibile del cursore misurandone la larghezza:
const sliderVisibleWidth = slider.offsetWidth;
Vogliamo anche la larghezza totale di tutti gli oggetti contenuti all'interno. Per mantenere il nostro giostra
funzione relativamente pulita, poniamo questo calcolo in una funzione separata nella parte superiore del nostro file.
Usando getBoundingClientRect
misurare il sinistra
offset del nostro primo oggetto e il destra
offset del nostro ultimo articolo, possiamo usare la differenza tra di loro per trovare la larghezza totale di tutti gli articoli.
function getTotalItemsWidth (items) const left = items [0] .getBoundingClientRect (); const right = items [items.length - 1] .getBoundingClientRect (); ritorno a destra - a sinistra;
Dopo il nostro sliderVisibleWidth
misurazione, scrivere:
const totalItemsWidth = getTotalItemsWidth (items);
Ora possiamo calcolare la distanza massima che consentirà al nostro carosello di scorrere. È la larghezza totale di tutti i nostri articoli, meno una piena larghezza del nostro cursore visibile. Questo fornisce un numero che consente di allineare l'elemento più a destra con il diritto del nostro cursore:
const maxXOffset = 0; const minXOffset = - (totalItemsWidth - sliderVisibleWidth);
Con queste misure sul posto, siamo pronti per iniziare a scorrere il nostro carosello.
translateX
Popmotion viene fornito con un renderer CSS per l'impostazione semplice e performante delle proprietà CSS. Inoltre ha una funzione di valore che può essere usata per tracciare numeri e, soprattutto (come vedremo presto), per interrogare la loro velocità.
Nella parte superiore del tuo file JavaScript, importali in questo modo:
const css, value = window.popmotion;
Quindi, sulla linea dopo aver impostato minXOffset
, crea un renderer CSS per il nostro cursore:
const sliderRenderer = css (slider);
E creare un valore
per monitorare l'offset x del nostro slider e aggiornare il cursore translateX
proprietà quando cambia:
const sliderX = value (0, (x) => sliderRenderer.set ('x', x));
Ora, spostare il cursore orizzontalmente è semplice come scrivere:
sliderX.set (-100);
Provalo!
Vogliamo che il nostro carosello inizi a scorrere quando a utentetrascina il cursore orizzontalmente e smette di scorrere quando un utente smette di toccare lo schermo. I nostri gestori di eventi avranno questo aspetto:
lascia agire; function stopTouchScroll () document.removeEventListener ('touchend', stopTouchScroll); function startTouchScroll (e) document.addEventListener ('touchend', stopTouchScroll); slider.addEventListener ('touchstart', startTouchScroll, passive: false);
Nel nostro startTouchScroll
funzione, vogliamo:
sliderX
.touchmove
evento per vedere se l'utente sta trascinando verticalmente o orizzontalmente.Dopo document.addEventListener
, Inserisci:
if (action) action.stop ();
Questo fermerà qualsiasi altra azione (come il rotolo di slancio alimentato dalla fisica che implementeremo stopTouchScroll
) dallo spostamento del cursore. Ciò consentirà all'utente di "catturare" immediatamente il dispositivo di scorrimento se scorre oltre un elemento o titolo su cui desidera fare clic.
Successivamente, dobbiamo memorizzare il punto di contatto dell'origine. Questo ci permetterà di vedere dove l'utente muove il dito dopo. Se si tratta di un movimento verticale, consentiremo lo scorrimento della pagina come al solito. Se si tratta di un movimento orizzontale, scorreremo invece il cursore.
Vogliamo condividere questo touchOrigin
tra i gestori di eventi. Quindi dopo lascia agire;
Inserisci:
let touchOrigin = ;
Di nuovo nel nostro startTouchScroll
gestore, aggiungere:
const touch = e.touches [0]; touchOrigin = x: touch.pageX, y: touch.pageY;
Ora possiamo aggiungere un touchmove
ascoltatore di eventi al documento
per determinare la direzione di trascinamento in base a questo touchOrigin
:
document.addEventListener ('touchmove', defineDragDirection);
Nostro determineDragDirection
la funzione misurerà la posizione del tocco successivo, verificherà che sia effettivamente spostata e, in tal caso, misurerà l'angolo per determinare se è verticale o orizzontale:
function defineDragDirection (e) const touch = e.changedTouches [0]; const touchLocation = x: touch.pageX, y: touch.pageY;
Popmotion include alcuni calcolatori utili per misurare cose come la distanza tra due coordinate x / y. Possiamo importare quelli come questo:
const calc, css, value = window.popmotion;
Quindi misurare la distanza tra i due punti è questione di usare il distanza
calcolatrice:
const distance = calc.distance (touchOrigin, touchLocation);
Ora se il tocco è stato spostato, possiamo annullare questo listener di eventi.
se (! distanza) ritorna; document.removeEventListener ('touchmove', defineDragDirection);
Misurare l'angolo tra i due punti con il angolo
calcolatrice:
const angle = calc.angle (touchOrigin, touchLocation);
Possiamo usarlo per determinare se questo angolo è un angolo orizzontale o verticale, passandolo alla funzione seguente. Aggiungi questa funzione all'inizio del nostro file:
function angleIsVertical (angle) const isUp = (angolo <= -90 + 45 && angle >= -90 - 45); const isDown = (angolo <= 90 + 45 && angle >= 90 - 45); return (isUp || isDown);
Questa funzione ritorna vero
se l'angolo fornito è compreso tra -90 +/- 45 gradi (verso l'alto) o 90 +/- 45 gradi (verso il basso). Quindi possiamo aggiungere un altro ritorno
clausola se questa funzione restituisce vero
.
se (angleIsVertical (angle)) restituisce;
Ora sappiamo che l'utente sta provando a scorrere il carosello, possiamo iniziare a tracciare il dito. Popmotion offre un'azione del puntatore che emetterà le coordinate x / y di un puntatore del mouse o del tocco.
Innanzitutto, importa pointer
:
const calc, css, pointer, value = window.popmotion;
Per tenere traccia dell'input tattile, fornire l'evento originario a pointer
:
action = pointer (e) .start ();
Vogliamo misurare l'iniziale X
posizione del nostro puntatore e applicare qualsiasi movimento al cursore. Per questo, possiamo usare un trasformatore chiamato applyOffset
.
I trasformatori sono funzioni pure che assumono un valore e lo restituiscono, sì trasformato. Per esempio: const double = (v) => v * 2
.
const calc, css, pointer, transform, value = window.popmotion; const applyOffset = transform;
applyOffset
è una funzione al curry. Ciò significa che quando lo chiamiamo, crea una nuova funzione che può quindi essere passata a un valore. Per prima cosa lo chiamiamo con un numero di cui vogliamo misurare l'offset, in questo caso il valore corrente di action.x
, e un numero per applicare quell'offset a. In questo caso, questo è il nostro sliderX
.
Quindi il nostro applyOffset
la funzione sarà simile a questa:
const applyPointerMovement = applyOffset (action.x.get (), sliderX.get ());
Ora possiamo usare questa funzione nel puntatore produzione
callback per applicare il movimento del puntatore al cursore.
action.output ((x) => slider.set (applyPointerMovement (x)));
Il carosello ora è trascinabile al tatto! Puoi testarlo utilizzando l'emulazione del dispositivo negli Strumenti per sviluppatori di Chrome.
Sembra un po 'janky, giusto? Potrebbe essersi verificato uno scorrimento simile a prima: si solleva il dito e lo scorrimento si interrompe. O lo scorrimento si interrompe e quindi una piccola animazione prende il sopravvento per simulare una continuazione dello scorrimento.
Non lo faremo. Possiamo usare l'azione fisica in Popmotion per prendere la vera velocità di sliderX
e applicare l'attrito ad esso per un periodo di tempo.
Innanzitutto, aggiungilo al nostro elenco sempre crescente di importazioni:
const calc, css, physics, pointer, value = window.popmotion;
Quindi, alla fine del nostro stopTouchScroll
funzione, aggiungere:
if (action) action.stop (); action = physics (from: sliderX.get (), velocity: sliderX.getVelocity (), friction: 0.2) .output ((v) => sliderX.set (v)) .start ();
Qui, a partire dal
e velocità
vengono impostati con il valore corrente e la velocità di sliderX
. Ciò garantisce che la nostra simulazione fisica abbia le stesse condizioni iniziali di partenza del movimento di trascinamento dell'utente.
attrito
è impostato come 0.2
. L'attrito è impostato come valore da 0
a 1
, con 0
non avere alcun attrito e 1
essendo l'attrito assoluto. Prova a giocare con questo valore per vedere il cambiamento apportato al "feeling" del carosello quando un utente interrompe il trascinamento.
Numeri più piccoli lo faranno sentire più leggeri, e numeri più grandi renderanno il movimento più pesante. Per un movimento a scorrimento, mi sento 0.2
colpisce un buon equilibrio tra irregolare e pigro.
Ma c'è un problema! Se hai giocato con il tuo nuovo carosello touch, è ovvio. Non abbiamo limitato il movimento, rendendo possibile letteralmente buttare via la tua giostra!
C'è un altro trasformatore per questo lavoro, morsetto
. Questa è anche una funzione al curry, ovvero se la chiamiamo con un valore minimo e massimo, ad esempio 0
e 1
, restituirà una nuova funzione. In questo esempio, la nuova funzione limiterà qualsiasi numero assegnato ad esso in mezzo 0
e 1
:
morsetto (0, 1) (5); // restituisce 1
Innanzitutto, importa morsetto
:
const applyOffset, clamp = transform;
Vogliamo utilizzare questa funzione di bloccaggio attraverso il nostro carosello, quindi aggiungi questa linea dopo averla definita minXOffset
:
const clampXOffset = clamp (minXOffset, maxXOffset);
Stiamo andando a modificare i due produzione
abbiamo impostato le nostre azioni utilizzando una composizione funzionale leggera con il tubo
trasformatore.
Quando chiamiamo una funzione, la scriviamo in questo modo:
foo (0);
Se vogliamo dare l'output di quella funzione a un'altra funzione, potremmo scrivere in questo modo:
bar (foo (0));
Questo diventa leggermente difficile da leggere e peggiora solo quando aggiungiamo sempre più funzioni.
Con tubo
, possiamo comporre una nuova funzione al di fuori di foo
e bar
che possiamo riutilizzare:
const foobar = pipe (foo, bar); foobar (0);
È anche scritto in un inizio naturale -> ordine di finitura, che rende più facile da seguire. Possiamo usare questo per comporre applyOffset
e morsetto
in una singola funzione. Importare tubo
:
const applyOffset, clamp, pipe = transform;
Sostituisci il produzione
richiamata del nostro pointer
con:
pipe ((x) => x, applyOffset (action.x.get (), sliderX.get ()), clampXOffset, (v) => sliderX.set (v))
E sostituire il produzione
callback di fisica
con:
pipe (clampXOffset, (v) => sliderX.set (v))
Questo tipo di composizione funzionale può creare in modo abbastanza preciso processi descrittivi, passo-passo, con funzioni più piccole e riutilizzabili.
Ora, quando trascini e getti la giostra, non si sposterà oltre i suoi limiti.
L'arresto brusco non è molto soddisfacente. Ma questo è un problema per una parte successiva!
Questo è tutto per la parte 1. Finora, abbiamo dato un'occhiata ai caroselli esistenti per vedere i punti di forza e di debolezza dei diversi approcci allo scrolling. Abbiamo utilizzato il tracciamento di input e la fisica di Popmotion per animare in modo performante il nostro carosello translateX
con scorrimento a sfioramento. Siamo stati anche introdotti alla composizione funzionale e alle funzioni curry.
Puoi prendere una versione commentata della "storia finora" su questo CodePen.
Nelle prossime puntate, vedremo:
Non vedo l'ora di vederti lì!