Carrousels zijn een nietje van streaming- en e-commercesites. Zowel Amazon als Netflix gebruiken ze als prominente navigatiehulpmiddelen. In deze zelfstudie evalueren we het interactieontwerp van beide en gebruiken we onze bevindingen om de perfecte carrousel te implementeren.
In deze tutorialserie leren we ook enkele functies van Popmotion, een JavaScript-bewegingsengine. Het biedt animatietools zoals tweens (nuttig voor paginering), aanwijzer volgen (om te scrollen) en lente-fysica (voor onze prachtige afwerking.)
Deel 1 zal evalueren hoe Amazon en Netflix scrollen hebben geïmplementeerd. Vervolgens implementeren we een carrousel die via aanraken kan worden geschoven.
Aan het einde van deze serie hebben we wiel- en touchpad-scroll, paginering, voortgangsbalken, toetsenbordnavigatie en enkele kleine details met behulp van veerfysica geïmplementeerd. We zullen ook zijn blootgesteld aan een aantal functionele basissamenstellingen.
Wat is ervoor nodig om een carrousel "perfect" te laten zijn? Het moet toegankelijk zijn door:
Tot slot gaan we de dingen nog een stapje verder en maken dit een zelfverzekerd, heerlijk stukje UX door de carrousel duidelijk en visceraal te laten reageren met veerfysica wanneer de schuifregelaar het einde heeft bereikt.
Laten we eerst de HTML en CSS krijgen die nodig zijn om een rudimentaire carrousel te bouwen door deze CodePen te beschrijven.
De Pen is opgezet met Sass voor het voorbewerken van CSS en Babel voor het transponeren van ES6 JavaScript. Ik heb ook Popmotion meegeleverd, waartoe je toegang hebt window.popmotion
.
U kunt de code kopiëren naar een lokaal project als u dat wilt, maar u moet ervoor zorgen dat uw omgeving Sass en ES6 ondersteunt. Je moet ook Popmotion installeren met npm installeer popmotion
.
Op elke pagina kunnen we veel carrousels hebben. We hebben dus een methode nodig om de status en functionaliteit van elk in te kapselen.
Ik ga een gebruiken fabrieksfunctie liever dan een klasse
. Fabrieksfuncties vermijden de noodzaak om het vaak verwarrende te gebruiken deze
sleutelwoord en vereenvoudigt de code voor de doeleinden van deze tutorial.
Voeg in je JavaScript-editor deze eenvoudige functie toe:
functie carrousel (container) carrousel (document.querySelector ('. container'));
We voegen hier onze carrouselspecifieke code toe carrousel
functie.
Onze eerste taak is om de carrousel te laten rollen. Er zijn twee manieren om dit aan te pakken:
De voor de hand liggende oplossing zou zijn om in te stellen overflow-x: scrollen
op de schuifregelaar. Dit zou native scrollen in alle browsers mogelijk maken, inclusief aanraak- en horizontale muiswielinrichtingen.
Er zijn echter nadelen aan deze aanpak:
Alternatief:
translateX
We kunnen ook de carrousels animeren translateX
eigendom. Dit zou zeer veelzijdig zijn omdat we precies het ontwerp kunnen implementeren dat we leuk vinden. translateX
is ook erg performant, in tegenstelling tot de CSS links
eigenschap kan deze worden afgehandeld door de GPU van het apparaat.
Nadeel is dat we de scrolfunctionaliteit opnieuw moeten toepassen met behulp van JavaScript. Dat is meer werk, meer code.
Zowel Amazon en Netflix carrousels maken verschillende compromissen bij het benaderen van dit probleem.
Amazone bezielt de carrousels links
eigenschap in de "bureaublad" -modus. animeren links
is een ongelooflijk slechte keuze, omdat het wijzigen ervan een herberekening van de lay-out triggert. Dit is CPU-intensief en oudere machines zullen moeite hebben om 60 fps te raken.
Degene die de beslissing heeft genomen om te bewegen links
in plaats van translateX
moet een echte idioot zijn (spoiler: Ik was het, terug in 2012. We waren niet zo verlicht in die dagen.)
Wanneer het carrousel een aanraakapparaat detecteert, gebruikt het de oorspronkelijke scrollfunctie van de browser. Het probleem met alleen inschakelen in de modus "mobiel" is dat desktopgebruikers met horizontale scrollwielen missen. Het betekent ook dat elke inhoud buiten de carrousel visueel moet worden afgesneden:
Netflix correct animeert de carrousels translateX
eigendom, en dit gebeurt op alle apparaten. Hierdoor kunnen ze een ontwerp hebben dat bloedt buiten de carrousel:
Hierdoor kunnen ze op hun beurt een chique ontwerp maken waarbij items buiten de x- en y-randen van de carrousel worden vergroot en de omliggende items uit de weg gaan:
Helaas is de herimplementatie van het scrollen door Netflix voor aanraakapparaten onbevredigend: het gebruikt een op bewegingen gebaseerd paginatiesysteem dat langzaam en omslachtig aanvoelt. Er is ook geen aandacht voor horizontale scrollwielen.
We kunnen het beter doen. Laten we coderen!
Onze eerste zet is om de .schuif
knooppunt. Terwijl we bezig zijn, nemen we de items die het bevat, zodat we de dimensie van de schuifregelaar kunnen achterhalen.
functie carousel (container) const slider = container.querySelector ('. slider'); const items = slider.querySelectorAll ('. item');
We kunnen het zichtbare gedeelte van de schuif bepalen door de breedte te meten:
const sliderVisibleWidth = slider.offsetWidth;
We willen ook de totale breedte van alle items die erin zijn opgenomen. Om onze te houden carrousel
functioneer relatief schoon, laten we deze berekening in een aparte functie boven in ons bestand zetten.
Door het gebruiken van getBoundingClientRect
om het te meten links
offset van ons eerste item en de rechts
offset van ons laatste item, kunnen we het verschil tussen hen gebruiken om de totale breedte van alle items te vinden.
function getTotalItemsWidth (items) const left = items [0] .getBoundingClientRect (); const right = items [items.length - 1] .getBoundingClientRect (); keer terug rechts - links;
Na onze sliderVisibleWidth
meting, schrijf:
const totalItemsWidth = getTotalItemsWidth (items);
We kunnen nu de maximale afstand berekenen die onze carrousel moet kunnen schuiven. Het is de totale breedte van al onze items, min één volle breedte van onze zichtbare schuifregelaar. Dit biedt een nummer waarmee het meest rechtse item kan worden uitgelijnd met het recht van onze schuifregelaar:
const maxXOffset = 0; const minXOffset = - (totalItemsWidth - sliderVisibleWidth);
Met deze metingen op zijn plaats, zijn we klaar om te beginnen met het scrollen van onze carrousel.
translateX
Popmotion wordt geleverd met een CSS-renderer voor de eenvoudige en performante instelling van CSS-eigenschappen. Het komt ook met een waardefunctie die kan worden gebruikt om nummers bij te houden en, belangrijker (zoals we snel zullen zien), om hun snelheid op te vragen.
Bovenaan je JavaScript-bestand importeer je ze als volgt:
const css, value = window.popmotion;
Daarna, op de lijn nadat we begonnen minXOffset
, maak een CSS-renderer aan voor onze slider:
const sliderRenderer = css (slider);
En maak een waarde
om de x-offset van onze schuifregelaar bij te houden en de schuifregelaars bij te werken translateX
eigendom wanneer het verandert:
const sliderX = waarde (0, (x) => sliderRenderer.set ('x', x));
Het verplaatsen van de schuifregelaar is nu net zo eenvoudig als schrijven:
sliderX.set (-100);
Probeer het!
We willen dat onze carrousel begint te scrollen wanneer een gebruikersleept de schuifregelaar horizontaal en stopt met scrollen wanneer een gebruiker het scherm niet meer aanraakt. Onze event-handlers zien er als volgt uit:
laat actie; function stopTouchScroll () document.removeEventListener ('touchend', stopTouchScroll); function startTouchScroll (e) document.addEventListener ('touchend', stopTouchScroll); slider.addEventListener ('touchstart', startTouchScroll, passive: false);
In onze startTouchScroll
functie, willen we:
sliderX
.touchmove
gebeurtenis om te zien of de gebruiker verticaal of horizontaal sleept.Na document.addEventListener
, toevoegen:
als (actie) action.stop ();
Dit stopt alle andere acties (zoals de door fysica aangedreven momentum scroll die we zullen implementeren stopTouchScroll
) om de schuifregelaar te verplaatsen. Hierdoor kan de gebruiker de schuifregelaar onmiddellijk "vangen" als deze voorbij een item of titel schuift waarop ze willen klikken.
Vervolgens moeten we het beginpunt van de oorsprong opslaan. Op die manier kunnen we zien waar de gebruiker zijn vinger naast zet. Als het een verticale beweging is, laten we het scrollen van de pagina zoals gebruikelijk. Als het een horizontale beweging is, scrollen we in plaats daarvan door de schuifregelaar.
We willen dit delen touchOrigin
tussen event handlers. Dus na laat actie;
toevoegen:
laat touchOrigin = ;
Terug in onze startTouchScroll
handler, voeg toe:
const touch = e.touches [0]; touchOrigin = x: touch.pageX, y: touch.pageY;
We kunnen nu een toevoegen touchmove
gebeurtenis luisteraar voor de document
om de draairichting op basis hiervan te bepalen touchOrigin
:
document.addEventListener ('touchmove', bepaalDragDirection);
Onze determineDragDirection
functie gaat de volgende aanraaklocatie meten, controleren of deze daadwerkelijk is verplaatst en, zo ja, de hoek meten om te bepalen of deze verticaal of horizontaal is:
functie bepalenDragDirectie (e) const touch = e.changedTouches [0]; const touchLocation = x: touch.pageX, y: touch.pageY;
Popmotion bevat een aantal handige calculators voor het meten van dingen zoals de afstand tussen twee x / y-coördinaten. We kunnen die als volgt importeren:
const calc, css, value = window.popmotion;
Het meten van de afstand tussen de twee punten is een kwestie van het gebruiken van de afstand
rekenmachine:
const afstand = calc.distance (touchOrigin, touchLocation);
Als de aanraking is verplaatst, kunnen we deze gebeurtenislistener uitschakelen.
als (! afstand) terugkomt; document.removeEventListener ('touchmove', bepaalDragDirection);
Meet de hoek tussen de twee punten met de hoek
rekenmachine:
const angle = calc.angle (touchOrigin, touchLocation);
We kunnen dit gebruiken om te bepalen of deze hoek een horizontale of verticale hoek is, door deze door te geven aan de volgende functie. Voeg deze functie toe aan de top van ons bestand:
function angleIsVertical (angle) const isUp = (hoek <= -90 + 45 && angle >= -90 - 45); const isDown = (hoek <= 90 + 45 && angle >= 90 - 45); return (isUp || isDown);
Deze functie retourneert waar
als de geleverde hoek binnen -90 +/- 45 graden (recht omhoog) of 90 +/- 45 graden (recht naar beneden) ligt. We kunnen dus een andere hoek toevoegen terugkeer
clausule als deze functie terugkeert waar
.
als (angleIsVertical (hoek)) terugkomt;
Nu weten we dat de gebruiker probeert door de carrousel te bladeren, we kunnen beginnen met het volgen van zijn vinger. Popmotion biedt een aanwijzeractie die de x / y-coördinaten van een muis of aanraakaanwijzer uitvoert.
Eerst importeren wijzer
:
const calc, css, pointer, value = window.popmotion;
Om de aanraakinvoer te volgen, geeft u de oorspronkelijke gebeurtenis aan wijzer
:
action = pointer (e) .start ();
We willen de initiaal meten X
positie van onze aanwijzer en past elke beweging op de schuifregelaar toe. Daarvoor kunnen we een transformator gebruiken genaamd applyOffset
.
Transformers zijn pure functies die een waarde aannemen en deze teruggeven - ja-getransformeerd. Bijvoorbeeld: const double = (v) => v * 2
.
const calc, css, pointer, transform, value = window.popmotion; const applyOffset = transformeren;
applyOffset
is een curried-functie. Dit betekent dat wanneer we het noemen, het een nieuwe functie creëert die vervolgens een waarde kan worden doorgegeven. We noemen het eerst met een nummer waarvan we de offset willen meten, in dit geval de huidige waarde van action.x
, en een nummer om die offset toe te passen. In dit geval is dat onze sliderX
.
Zo onze applyOffset
functie ziet er als volgt uit:
const applyPointerMovement = applyOffset (action.x.get (), sliderX.get ());
We kunnen deze functie nu in de aanwijzer gebruiken uitgang
terugbellen om wijzerbeweging toe te passen op de schuifregelaar.
action.output ((x) => slider.set (applyPointerMovement (x)));
De carrousel kan nu worden versleept door aanraking! U kunt dit testen door apparaatemulatie te gebruiken in de ontwikkelaarstools van Chrome.
Het voelt een beetje janky, toch? Mogelijk bent u scrollen tegengekomen dat zich eerder zo heeft gevoeld: u heft uw vinger op en het scrollen stopt. Of het scrollen stopt dood en vervolgens neemt een kleine animatie het voortzetting van het scrollen over.
Dat gaan we niet doen. We kunnen de natuurkundige actie in Popmotion gebruiken om de ware snelheid van te nemen sliderX
en wrijving toepassen op het gedurende een tijdsduur.
Voeg het eerst toe aan onze steeds groter wordende lijst van importen:
const calc, css, physics, pointer, value = window.popmotion;
Vervolgens, aan het einde van onze stopTouchScroll
functie, voeg toe:
als (actie) action.stop (); action = physics (from: sliderX.get (), velocity: sliderX.getVelocity (), friction: 0.2) .output ((v) => sliderX.set (v)) .start ();
Hier, van
en snelheid
worden ingesteld met de huidige waarde en snelheid van sliderX
. Dit zorgt ervoor dat onze simulatie van fysica dezelfde initiële startvoorwaarden heeft als de sleepbeweging van de gebruiker.
wrijving
wordt ingesteld als 0.2
. Wrijving wordt ingesteld als een waarde van 0
naar 1
, met 0
helemaal geen wrijving zijn en 1
absoluut wrijving zijn. Probeer eens rond te spelen met deze waarde om de verandering te zien die het maakt naar het "gevoel" van de carrousel wanneer een gebruiker stopt met slepen.
Kleinere nummers zullen het lichter laten aanvoelen, en grotere aantallen zullen beweging zwaarder maken. Voor een rollende beweging voel ik 0.2
heeft een mooie balans tussen grillig en traag.
Maar er is een probleem! Als je met je nieuwe aanraakcarrousel hebt gespeeld, is dat duidelijk. We hebben geen beweging begrensd, waardoor het mogelijk is om je carrousel letterlijk weg te gooien!
Er is nog een transformator voor deze klus, klem
. Dit is ook een curried-functie, wat betekent dat als we het een min- en max-waarde noemen, zeggen 0
en 1
, het zal een nieuwe functie teruggeven. In dit voorbeeld beperkt de nieuwe functie elk gegeven getal tot tussen 0
en 1
:
klem (0, 1) (5); // geeft als resultaat 1
Eerst importeren klem
:
const applyOffset, clamp = transformeren;
We willen deze klemfunctie gebruiken in onze carrousel, dus voeg deze regel toe nadat we deze hebben gedefinieerd minXOffset
:
const clampXOffset = clamp (minXOffset, maxXOffset);
We gaan de twee wijzigen uitgang
we hebben onze acties opgezet met behulp van een lichte functionele compositie bij de pijp
transformator.
Wanneer we een functie noemen, schrijven we het als volgt:
foo (0);
Als we de uitvoer van die functie aan een andere functie willen geven, kunnen we dat zo schrijven:
bar (foo (0));
Dit wordt enigszins moeilijk te lezen en het wordt alleen maar erger naarmate we meer en meer functies toevoegen.
Met pijp
, we kunnen een nieuwe functie samenstellen uit foo
en bar
welke we kunnen hergebruiken:
const foobar = pijp (foo, bar); foobar (0);
Het is ook geschreven in een natuurlijke start -> afwerkingvolgorde, waardoor het gemakkelijker te volgen is. We kunnen dit gebruiken om te componeren applyOffset
en klem
in een enkele functie. Importeren pijp
:
const applyOffset, clamp, pipe = transform;
Vervang de uitgang
terugbellen van onze wijzer
met:
pipe ((x) => x, applyOffset (action.x.get (), sliderX.get ()), clampXOffset, (v) => sliderX.set (v))
En vervang de uitgang
terugbellen van fysica
met:
pipe (clampXOffset, (v) => sliderX.set (v))
Dit soort functionele samenstelling kan vrij beschrijvende, stapsgewijze processen creëren uit kleinere, herbruikbare functies.
Als u de carrousel sleept en weggooit, wijkt deze niet buiten de grenzen.
De abrupte stop is niet erg bevredigend. Maar dat is een probleem voor een later deel!
Dat is alles voor deel 1. Tot nu toe hebben we een kijkje genomen bij bestaande carrousels om de sterke en zwakke punten van verschillende benaderingen van scrollen te zien. We hebben de input-tracking en physics van Popmotion gebruikt om onze carrousels op een performante manier te animeren translateX
met touch-scrolling. We hebben ook kennis gemaakt met functionele compositie en gecaste functies.
Je kunt een commentaarversie van het "verhaal tot nu toe" op deze CodePen nemen.
In aankomende termijnen zullen we kijken naar:
Ik kijk ernaar uit je daar te zien!