Questa è la terza e ultima parte della nostra serie di tutorial Create the Perfect Carousel. Nella parte 1, abbiamo valutato i caroselli su Netflix e Amazon, due dei caroselli più usati al mondo. Abbiamo impostato la nostra giostra e implementato il touch scroll.
Quindi, nella parte 2, abbiamo aggiunto scorrimento orizzontale del mouse, impaginazione e un indicatore di avanzamento. Boom.
Ora, nella parte finale, esamineremo il mondo oscuro e spesso dimenticato dell'accessibilità della tastiera. Regoleremo il nostro codice per rimisurare il carosello quando cambia la dimensione del viewport. E, infine, faremo qualche tocco finale usando la fisica di primavera.
Puoi riprendere da dove abbiamo lasciato con questo CodePen.
È vero che la maggior parte degli utenti non si affida alla navigazione da tastiera, quindi purtroppo a volte ci dimentichiamo dei nostri utenti che lo fanno. In alcuni paesi, l'accesso a un sito Web inaccessibile potrebbe essere illegale. Ma peggio, è una mossa di cazzo.
La buona notizia è che di solito è facile da implementare! In effetti, i browser fanno la maggior parte del lavoro per noi. Seriamente: prova a sfogliare il carosello che abbiamo realizzato. Perché abbiamo usato il markup semantico, è già possibile!
Tranne che, noterai, i nostri pulsanti di navigazione scompaiono. Questo perché il browser non consente di concentrarsi su un elemento al di fuori del nostro viewport. Quindi anche se abbiamo overflow: nascosto
impostato, non siamo in grado di scorrere la pagina orizzontalmente; altrimenti, la pagina scorrerà effettivamente per mostrare l'elemento con il focus.
Va bene, e secondo me sarebbe qualificato come "utile", sebbene non esattamente delizioso.
Anche il carosello di Netflix funziona in questo modo. Ma poiché la maggior parte dei loro titoli sono pigri e sono passivamente accessibili da tastiera (il che significa che non hanno scritto alcun codice specifico per gestirli), non possiamo selezionare alcun titolo oltre i pochi che abbiamo già caricato. Sembra anche terribile:
Possiamo fare di meglio.
messa a fuoco
EventoPer fare questo, ascolteremo il messa a fuoco
evento che spara su qualsiasi oggetto nel carosello. Quando un oggetto riceve attenzione, lo interrogheremo per la sua posizione. Quindi, controlleremo contro sliderX
e sliderVisibleWidth
per vedere se quell'elemento si trova all'interno della finestra visibile. Se non lo è, lo impagheremo usando lo stesso codice che abbiamo scritto nella parte 2.
Alla fine di giostra
funzione, aggiungi questo listener di eventi:
slider.addEventListener ('focus', onFocus, true);
Noterai che abbiamo fornito un terzo parametro, vero
. Anziché aggiungere un listener di eventi a ciascun elemento, possiamo utilizzare la cosiddetta delega degli eventi per ascoltare gli eventi su un solo elemento, il loro genitore diretto. Il messa a fuoco
l'evento non bolle, quindi vero
sta dicendo all'ascoltatore dell'evento di ascoltare il catturare palcoscenico, il palcoscenico in cui l'evento spara su ogni elemento dal finestra
attraverso il bersaglio (in questo caso, l'oggetto che riceve attenzione).
Sopra il nostro crescente numero di ascoltatori di eventi, aggiungi il onFocus
funzione:
function onFocus (e)
Lavoreremo in questa funzione per il resto di questa sezione.
Dobbiamo misurare l'articolo sinistra
e destra
compensare e verificare se uno dei due punti si trova al di fuori dell'area attualmente visibile.
L'articolo è fornito dagli eventi bersaglio
parametro, e possiamo misurarlo con getBoundingClientRect
:
const left, right = e.target.getBoundingClientRect ();
sinistra
e destra
sono relativi al viewport, non il cursore. Quindi abbiamo bisogno di prendere il contenitore della giostra sinistra
compensare per tener conto di ciò. Nel nostro esempio, questo sarà 0
, ma per rendere il carosello robusto, dovrebbe tener conto di essere posizionato ovunque.
const carouselLeft = container.getBoundingClientRect (). left;
Ora, possiamo fare un semplice controllo per vedere se l'oggetto si trova all'esterno dell'area visibile del cursore e impaginare in quella direzione:
se (a sinistra < carouselLeft) gotoPrev(); else if (right > carouselLeft + sliderVisibleWidth) gotoNext ();
Ora, mentre ci aggiriamo, il carousel confluisce tranquillamente intorno con il nostro focus sulla tastiera! Solo poche righe di codice per mostrare più amore ai nostri utenti.
Potresti aver notato come segui questo tutorial che se ridimensioni la finestra del browser, il carousel non impagina più correttamente. Questo perché abbiamo misurato la sua larghezza rispetto alla sua area visibile solo una volta, al momento dell'inizializzazione.
Per assicurarci che il nostro carosello si comporti correttamente, dobbiamo sostituire alcuni dei nostri codici di misurazione con un nuovo listener di eventi che si attiva quando il finestra
ridimensiona.
Ora, vicino all'inizio del tuo giostra
funzione, subito dopo la linea in cui definiamo barra di avanzamento
, vogliamo sostituire tre di questi const
misure con permettere
, perché li cambieremo quando cambierà la vista:
const totalItemsWidth = getTotalItemsWidth (items); const maxXOffset = 0; let minXOffset = 0; lasciare sliderVisibleWidth = 0; lasciare clampXOffset;
Quindi, possiamo spostare la logica che in precedenza ha calcolato questi valori su un nuovo measureCarousel
funzione:
function measureCarousel () sliderVisibleWidth = slider.offsetWidth; minXOffset = - (totalItemsWidth - sliderVisibleWidth); clampXOffset = clamp (minXOffset, maxXOffset);
Vogliamo richiamare immediatamente questa funzione in modo da impostare questi valori sull'inizializzazione. Nella riga successiva, chiama measureCarousel
:
measureCarousel ();
La giostra dovrebbe funzionare esattamente come prima. Per aggiornare il ridimensionamento della finestra, aggiungiamo semplicemente questo listener di eventi alla fine del nostro giostra
funzione:
window.addEventListener ('ridimensiona', measureCarousel);
Ora, se ridimensioni il carousel e provi a impaginare, continuerà a funzionare come previsto.
Vale la pena considerare che nel mondo reale, potresti avere più caroselli sulla stessa pagina, moltiplicando l'impatto sulle prestazioni di questo codice di misura di quella quantità.
Come abbiamo brevemente discusso nella parte 2, non è saggio eseguire calcoli pesanti più spesso di quanto si deve. Con gli eventi di puntatore e scorrimento, abbiamo detto che si desidera eseguirli una volta per frame per mantenere 60fps. Gli eventi di ridimensionamento sono leggermente diversi in quanto l'intero documento rifletterà, probabilmente il momento più intenso in termini di risorse che una pagina Web incontrerà.
Non è necessario rimisurare il carosello finché l'utente non ha ridimensionato la finestra, poiché nel frattempo non interagiranno con esso. Possiamo avvolgere il nostro measureCarousel
funzione in una funzione speciale chiamata a antirimbalzo.
Una funzione di rimbalzo dice essenzialmente: "Spara questa funzione solo quando non è stata richiamata X
millisecondi. "Puoi leggere di più su debounce sull'eccellente primer di David Walsh e prendere anche qualche codice di esempio.
Finora, abbiamo creato un carosello piuttosto buono. È accessibile, si anima bene, funziona su touch e mouse e offre una grande flessibilità di design in un modo in cui i caroselli a scorrimento nativo non consentono.
Ma questa non è la serie di tutorial "Crea un bel carosello". È tempo per noi di sfoggiare un po ', e per farlo, abbiamo un'arma segreta. Springs.
Aggiungeremo due interazioni usando le molle. Uno per il tocco e uno per l'impaginazione. Entrambi faranno sapere all'utente, in modo divertente e giocoso, che hanno raggiunto la fine della giostra.
Innanzitutto, aggiungiamo un rimorchiatore in stile iOS quando un utente tenta di far scorrere il cursore oltre i suoi limiti. Attualmente stiamo riducendo il touch scroll utilizzando clampXOffset
. Invece, sostituiamo questo con un codice che applica un rimorchiatore quando l'offset calcolato è al di fuori dei suoi limiti.
Per prima cosa, dobbiamo importare la nostra primavera. C'è un trasformatore chiamato nonlinearSpring
che applica una forza esponenzialmente crescente contro il numero che gli forniamo, verso un origine
. Il che significa che quanto più tiriamo il cursore, tanto più lo tirerà indietro. Possiamo importarlo in questo modo:
const applyOffset, clamp, nonlinearSpring, pipe = transform;
Nel determineDragDirection
funzione, abbiamo questo codice:
action.output (pipe ((x) => x, applyOffset (action.x.get (), sliderX.get ()), clampXOffset, (v) => sliderX.set (v)));
Appena sopra di esso, creiamo le nostre due molle, una per ogni limite di scorrimento del carosello:
const elasticity = 5; const tugLeft = nonlinearSpring (elasticity, maxXOffset); const tugRight = nonlinearSpring (elasticity, minXOffset);
Decidere su un valore per elasticità
è una questione di giocare e vedere ciò che sembra giusto. Un numero troppo basso e la molla è troppo rigida. Troppo alto e non noterai il suo strattone, o peggio, spingerà il cursore ancora più lontano dal dito dell'utente!
Ora abbiamo solo bisogno di scrivere una semplice funzione che applicherà una di queste molle se il valore fornito è al di fuori dell'intervallo consentito:
const applySpring = (v) => if (v> maxXOffset) return tugLeft (v); se (v < minXOffset) return tugRight(v); return v; ;
Possiamo sostituire clampXOffset
nel codice sopra con applySpring
. Ora, se si fa scorrere il cursore oltre i suoi limiti, esso tornerà indietro!
Tuttavia, quando lasciamo andare la primavera, questa specie di scatta senza tante cerimonie torna al suo posto. Vogliamo modificare il nostro stopTouchScroll
funzione, che attualmente gestisce lo scorrimento del momento, per verificare se il cursore è ancora al di fuori dell'intervallo consentito e, in tal caso, applicare una molla con fisica
azione invece.
Il fisica
l'azione è anche in grado di modellare le molle. Abbiamo solo bisogno di fornirlo primavera
e a
proprietà.
Nel stopTouchScroll
, sposta la pergamena esistente fisica
inizializzazione ad un pezzo di logica che ci assicura che siamo entro i limiti di scorrimento:
const currentX = sliderX.get (); se (currentX < minXOffset || currentX > maxXOffset) else action = physics (from: currentX, velocity: sliderX.getVelocity (), friction: 0.2). output (pipe (clampXOffset, (v) => sliderX.set (v))). ();
All'interno della prima clausola del Se
dichiarazione, sappiamo che il cursore è al di fuori dei limiti di scorrimento, quindi possiamo aggiungere la nostra molla:
action = physics (from: currentX, a: (currentX < minXOffset) ? minXOffset : maxXOffset, spring: 800, friction: 0.92 ).output((v) => sliderX.set (v)) .start ();
Vogliamo creare una molla che si senta scattante e reattiva. Ho scelto un livello relativamente alto primavera
valore per avere un "pop" stretto, e ho abbassato il attrito
a 0.92
per consentire un piccolo rimbalzo. È possibile impostare questo a 1
per eliminare completamente il rimbalzo.
Come un po 'di compiti a casa, prova a sostituire il clampXOffset
nel produzione
funzione della pergamena fisica
con una funzione che innesca una molla simile quando l'offset x raggiunge i suoi limiti. Invece di fermarsi bruscamente, prova a farlo rimbalzare piano alla fine.
Toccare gli utenti ottengono sempre la bontà primaverile, giusto? Condividiamo questo amore con gli utenti desktop rilevando quando il carosello è ai suoi limiti di scorrimento e avendo un rimorchiatore indicativo per mostrare chiaramente e con sicurezza all'utente che si trovano alla fine.
Per prima cosa, vogliamo disabilitare i pulsanti di impaginazione quando il limite è stato raggiunto. Per prima cosa aggiungiamo una regola CSS che stili i pulsanti per mostrare che sono Disabilitato
. Nel pulsante
regola, aggiungere:
transizione: sfondo 200 ms lineare; & .disabled background: #eee;
Stiamo usando una classe qui invece della più semantica Disabilitato
attributo perché vogliamo ancora catturare gli eventi click, che, come suggerisce il nome, Disabilitato
bloccherebbe.
Aggiungi questo Disabilitato
classe al pulsante Indietro, perché ogni giostra inizia la vita con a 0
compensare:
Verso la cima di giostra
, creare una nuova funzione chiamata checkNavButtonStatus
. Vogliamo che questa funzione controlli semplicemente il valore fornito minXOffset
e maxXOffset
e imposta il pulsante Disabilitato
classificare di conseguenza:
function checkNavButtonStatus (x) if (x <= minXOffset) nextButton.classList.add('disabled'); else nextButton.classList.remove('disabled'); if (x >= maxXOffset) prevButton.classList.add ('disabled'); else prevButton.classList.remove ('disabled');
Sarebbe allettante chiamarlo ogni volta sliderX
i cambiamenti. Se lo facessimo, i pulsanti inizieranno a lampeggiare ogni volta che una molla oscillerà attorno ai bordi dello scroll. Sarebbe anche strano comportamento se uno dei pulsanti è stato premuto durante una di quelle animazioni primaverili. Il rimorchiatore "scroll end" dovrebbe sempre sparare se siamo alla fine del carosello, anche se c'è un'animazione a molla che la allontana dalla fine assoluta.
Quindi dobbiamo essere più selettivi su quando chiamare questa funzione. Sembra ragionevole chiamarlo:
Sull'ultima riga del onWheel
, Inserisci checkNavButtonStatus (newX);
.
Sull'ultima riga di vai a
, Inserisci checkNavButtonStatus (TargetX);
.
E infine, alla fine di determineDragDirection
, e nella clausola scroll di slancio (il codice all'interno del altro
) di stopTouchScroll
, sostituire:
(v) => sliderX.set (v)
Con:
(v) => sliderX.set (v); checkNavButtonStatus (v);
Ora non resta che modificare gotoPrev
e gotoNext
per controllare la loro classList del pulsante di attivazione per Disabilitato
e solo paginare se è assente:
const gotoNext = (e) =>! e.target.classList.contains ('disabled')? goto (1): notifyEnd (-1, maxXOffset); const gotoPrev = (e) =>! e.target.classList.contains ('disabled')? goto (-1): notifyEnd (1, minXOffset);
Il notifyEnd
la funzione è solo un'altra fisica
primavera, e sembra così:
function notifyEnd (delta, targetOffset) if (action) action.stop (); action = physics (from: sliderX.get (), a: targetOffset, velocity: 2000 * delta, spring: 300, friction: 0.9) .output ((v) => sliderX.set (v)) .start ( );
Gioca con quello, e ancora, modifica il fisica
params di tuo gradimento.
Rimane solo un piccolo bug. Quando il cursore scatta oltre il limite più a sinistra, la barra di avanzamento viene invertita. Possiamo sistemarlo rapidamente sostituendo:
progressBarRenderer.set ('scaleX', progress);
Con:
progressBarRenderer.set ('scaleX', Math.max (progress, 0));
Noi potevaimpedirgli di rimbalzare dall'altra parte, ma personalmente penso che sia piuttosto bello che rifletta il movimento della molla. Sembra strano quando si capovolge.
Con le applicazioni a pagina singola, i siti Web durano più a lungo nella sessione di un utente. Spesso, anche quando la "pagina" cambia, stiamo ancora eseguendo lo stesso runtime JS del carico iniziale. Non possiamo fare affidamento su una lavagna pulita ogni volta che l'utente fa clic su un link e ciò significa che dobbiamo ripulire noi stessi per evitare che gli ascoltatori di eventi attivino elementi morti.
In React, questo codice è inserito nel file componentWillLeave
metodo. Vue utilizza beforeDestroy
. Questa è una pura implementazione JS, ma possiamo ancora fornire un metodo destroy che funzionerebbe allo stesso modo in entrambi i framework.
Finora, il nostro giostra
la funzione non ha restituito nulla. Cambiamo questo.
Innanzitutto, cambia la linea finale, la linea che chiama giostra
, a:
const destroyCarousel = carousel (document.querySelector ('. container'));
Torneremo solo da una cosa giostra
, una funzione che disimpegna tutti i nostri ascoltatori di eventi. Alla fine del giostra
funzione, scrivi:
return () => container.removeEventListener ('touchstart', startTouchScroll); container.removeEventListener ('wheel', onWheel); nextButton.removeEventListener ('click', gotoNext); prevButton.removeEventListener ('click', gotoPrev); slider.removeEventListener ('focus', onFocus); window.removeEventListener ('resize', measureCarousel); ;
Ora, se chiami destroyCarousel
e prova a giocare con la giostra, non succede nulla! È quasi un po 'triste vederlo così.
Accidenti. Era molto! Quanto siamo arrivati. Puoi vedere il prodotto finito in questo CodePen. In questa parte finale, abbiamo aggiunto l'accessibilità della tastiera, rimisurando il carosello quando cambia la vista, alcune aggiunte divertenti con la fisica di primavera e il passo straziante ma necessario di strappare di nuovo tutto.
Spero che questo tutorial ti sia piaciuto tanto quanto mi è piaciuto scriverlo. Mi piacerebbe sentire i tuoi pensieri su ulteriori modi in cui potremmo migliorare l'accessibilità o aggiungere piccoli tocchi divertenti.