Les carrousels sont un aliment de base des sites de streaming et de commerce électronique. Amazon et Netflix les utilisent tous deux comme outils de navigation de premier plan. Dans ce didacticiel, nous évaluerons la conception des interactions entre les deux et utiliserons nos résultats pour mettre en œuvre le carrousel parfait..
Dans cette série de didacticiels, nous allons également apprendre quelques fonctions de Popmotion, un moteur de mouvement JavaScript. Il propose des outils d'animation tels que les tweens (utiles pour la pagination), le suivi du pointeur (pour le défilement) et la physique du printemps (pour nos ravissantes touches finales).
La première partie évaluera comment Amazon et Netflix ont implémenté le défilement. Nous allons ensuite mettre en place un carrousel qui peut être fait défiler au toucher.
À la fin de cette série, nous aurons implémenté le défilement des roues et du pavé tactile, la pagination, les barres de progression, la navigation au clavier et quelques petites manipulations utilisant la physique des ressorts. Nous aurons également été exposés à une composition fonctionnelle de base.
Que faut-il pour qu'un carrousel soit "parfait"? Il doit être accessible par:
Enfin, nous allons faire avancer les choses encore plus loin et en faire un UX confiant et délicieux en faisant en sorte que le carrousel réponde clairement et viscéralement à la physique du printemps lorsque le curseur aura atteint la fin..
Premièrement, obtenons le code HTML et CSS nécessaire pour construire un carrousel rudimentaire en créant ce CodePen.
Le stylo est configuré avec Sass pour le pré-traitement CSS et Babel pour la transcription de JavaScript ES6. J'ai également inclus Popmotion, qui peut être consulté avec window.popmotion
.
Vous pouvez copier le code dans un projet local si vous préférez, mais vous devez vous assurer que votre environnement prend en charge Sass et ES6. Vous aurez également besoin d’installer Popmotion avec npm installer popmotion
.
Sur une page donnée, nous pourrions avoir de nombreux carrousels. Nous avons donc besoin d’une méthode pour encapsuler l’état et les fonctionnalités de chaque.
Je vais utiliser un fonction d'usine Plutôt qu'un classe
. Les fonctions d’usine évitent d’utiliser des fonctions souvent source de confusion ce
mot-clé et simplifiera le code pour les besoins de ce tutoriel.
Dans votre éditeur JavaScript, ajoutez cette fonction simple:
fonction carrousel (conteneur) carrousel (document.querySelector ('. conteneur'));
Nous allons ajouter notre code spécifique au carrousel à l'intérieur de cette carrousel
une fonction.
Notre première tâche consiste à faire défiler le carrousel. Il y a deux façons de s'y prendre:
La solution évidente serait de définir overflow-x: faire défiler
sur le curseur. Cela permettrait le défilement natif sur tous les navigateurs, y compris les périphériques tactiles et horizontaux..
Cette approche présente toutefois des inconvénients:
Alternativement:
translateX
Nous pourrions aussi animer le carrousel translateX
propriété. Ce serait très polyvalent car nous serions en mesure de mettre en œuvre exactement le design que nous aimons. translateX
est également très performant, contrairement au CSS la gauche
propriété, il peut être géré par le GPU du périphérique.
En revanche, il faudrait réimplémenter la fonctionnalité de défilement à l'aide de JavaScript. C'est plus de travail, plus de code.
Les carrousels Amazon et Netflix font des compromis différents en abordant ce problème.
Amazone anime le carrousel la gauche
propriété en mode "bureau". Animer la gauche
est un choix incroyablement pauvre, car son changement déclenche un nouveau calcul de la mise en page. Cela nécessite beaucoup de temps processeur, et les anciennes machines auront du mal à atteindre 60 images par seconde.
Celui qui a pris la décision d'animer la gauche
au lieu de translateX
doit être un vrai idiot (becquet: c'était moi, en 2012. Nous n'étions pas aussi éclairés à l'époque.)
Lorsqu'il détecte un périphérique tactile, le carrousel utilise le défilement natif du navigateur. Le problème avec l'activation de ce mode en mode "mobile" est que les utilisateurs de bureau dotés d'une molette de défilement horizontale ne sont pas pris en compte. Cela signifie également que tout contenu situé en dehors du carrousel devra être coupé visuellement:
Netflix anime correctement le carrousel translateX
propriété, et il le fait sur tous les appareils. Cela leur permet d'avoir un design qui saigne à l'extérieur du carrousel:
Cela leur permet à leur tour de créer un design de fantaisie où les éléments sont agrandis en dehors des bords x et y du carrousel et où les éléments environnants sortent de leur chemin:
Malheureusement, la réimplémentation du défilement par Netflix pour les appareils tactiles n’est pas satisfaisante: elle utilise un système de pagination basé sur les gestes qui semble lent et lourd. Il n'y a également aucune considération pour les roues de défilement horizontales.
Nous pouvons faire mieux. Codons!
Notre premier geste est de saisir le .curseur
nœud. Pendant que nous y sommes, prenons les éléments qu'il contient pour déterminer la dimension du curseur..
fonction carrousel (conteneur) const slider = container.querySelector ('. slider'); const items = slider.querySelectorAll ('. item');
Nous pouvons déterminer la zone visible du curseur en mesurant sa largeur:
const sliderVisibleWidth = slider.offsetWidth;
Nous voudrons également connaître la largeur totale de tous les éléments contenus à l'intérieur. Pour garder notre carrousel
fonctionner relativement propre, mettons ce calcul dans une fonction séparée en haut de notre fichier.
En utilisant getBoundingClientRect
pour mesurer le la gauche
offset de notre premier article et la droite
offset de notre dernier article, nous pouvons utiliser la différence entre eux pour trouver la largeur totale de tous les articles.
function getTotalItemsWidth (items) const left = items [0] .getBoundingClientRect (); const right = items [items.length - 1] .getBoundingClientRect (); retournez droite - gauche;
Après notre sliderVisibleWidth
mesure, écrivez:
const totalItemsWidth = getTotalItemsWidth (items);
Nous pouvons maintenant déterminer la distance maximale que notre carrousel devrait pouvoir parcourir. C'est la largeur totale de tous nos articles, moins une pleine largeur de notre curseur visible. Cela fournit un nombre qui permet à l'élément le plus à droite de s'aligner sur la droite de notre curseur:
const maxXOffset = 0; const minXOffset = - (totalItemsWidth - sliderVisibleWidth);
Avec ces mesures en place, nous sommes prêts à commencer à faire défiler notre carrousel..
translateX
Popmotion est livré avec un moteur de rendu CSS pour un paramétrage simple et performant des propriétés CSS. Il est également livré avec une fonction value qui peut être utilisée pour suivre les nombres et, surtout (comme nous le verrons bientôt), pour interroger leur vitesse..
En haut de votre fichier JavaScript, importez-les comme suit:
const css, valeur = window.popmotion;
Ensuite, sur la ligne après avoir défini minXOffset
, créer un moteur de rendu CSS pour notre curseur:
const sliderRenderer = css (slider);
Et créer un valeur
pour suivre le décalage de notre curseur et mettre à jour le curseur translateX
propriété quand il change:
const sliderX = valeur (0, (x) => sliderRenderer.set ('x', x));
Maintenant, déplacer le curseur horizontalement est aussi simple que d’écrire:
sliderX.set (-100);
L'essayer!
Nous voulons que notre carrousel commence à défiler quand un utilisateurfait glisser le curseur horizontalement et pour arrêter le défilement lorsqu'un utilisateur cesse de toucher l'écran. Nos gestionnaires d'événements ressembleront à ceci:
laisser l'action; function stopTouchScroll () document.removeEventListener ('touchend', stopTouchScroll); function startTouchScroll (e) document.addEventListener ('touchend', stopTouchScroll); slider.addEventListener ('touchstart', startTouchScroll, passif: false);
Dans notre startTouchScroll
fonction, nous voulons:
sliderX
.toucher
événement pour voir si l'utilisateur fait glisser verticalement ou horizontalement.Après document.addEventListener
, ajouter:
if (action) action.stop ();
Cela arrêtera toutes les autres actions (comme le défilement de momentum physique que nous allons implémenter dans stopTouchScroll
) de déplacer le curseur. Cela permettra à l'utilisateur de "rattraper" immédiatement le curseur s'il fait défiler un élément ou un titre sur lequel il souhaite cliquer..
Ensuite, nous devons stocker le point de contact d'origine. Cela nous permettra de voir où l’utilisateur déplacera ensuite son doigt. S'il s'agit d'un mouvement vertical, nous allons permettre le défilement de la page comme d'habitude. Si c'est un mouvement horizontal, nous allons faire défiler le curseur.
Nous voulons partager ceci TouchOrigin
entre les gestionnaires d'événements. Donc après laisser l'action;
ajouter:
laissez touchOrigin = ;
De retour dans notre startTouchScroll
gestionnaire, ajouter:
const touch = e.touches [0]; touchOrigin = x: touch.pageX, y: touch.pageY;
Nous pouvons maintenant ajouter un toucher
écouteur d'événement à la document
pour déterminer la direction de traînée basée sur cette TouchOrigin
:
document.addEventListener ('touchmove', DetermineDragDirection);
Notre DetermineDragDirection
function va mesurer le prochain emplacement tactile, vérifier qu'il a bien été déplacé et, le cas échéant, mesurer l'angle pour déterminer s'il est vertical ou horizontal:
fonction DetermDragDirection (e) const touch = e.changedTouches [0]; const touchLocation = x: touch.pageX, y: touch.pageY;
Popmotion inclut des calculatrices utiles pour mesurer des éléments tels que la distance entre deux coordonnées x / y. Nous pouvons importer ceux-ci comme ceci:
const calc, css, valeur = window.popmotion;
Ensuite, mesurer la distance entre les deux points consiste à utiliser le distance
calculatrice:
const distance = calc.distance (touchOrigin, touchLocation);
Maintenant, si le toucher a été déplacé, nous pouvons désactiver cet écouteur d'événement.
si (! distance) retourne; document.removeEventListener ('touchmove', DetermineDragDirection);
Mesurer l’angle entre les deux points avec le angle
calculatrice:
const angle = calc.angle (touchOrigin, touchLocation);
Nous pouvons utiliser cela pour déterminer si cet angle est un angle horizontal ou vertical, en le passant à la fonction suivante. Ajoutez cette fonction tout en haut de notre fichier:
fonction angleIsVertical (angle) const isUp = (angle <= -90 + 45 && angle >= -90 - 45); const isDown = (angle <= 90 + 45 && angle >= 90 - 45); return (isUp || isDown);
Cette fonction retourne vrai
si l'angle fourni est compris entre -90 +/- 45 degrés (tout en haut) ou 90 +/- 45 degrés (tout en bas), nous pouvons donc en ajouter un autre revenir
clause si cette fonction retourne vrai
.
si (angleIsVertical (angle)) retourne;
Maintenant que nous savons que l'utilisateur essaie de faire défiler le carrousel, nous pouvons commencer à suivre son doigt. Popmotion propose une action de pointeur qui générera les coordonnées x / y d'un pointeur de souris ou tactile.
Tout d'abord, importer aiguille
:
const calc, css, pointeur, valeur = window.popmotion;
Pour suivre la saisie tactile, fournissez l'événement d'origine à aiguille
:
action = pointeur (e) .start ();
Nous voulons mesurer l'initiale X
position de notre pointeur et appliquer n'importe quel mouvement au curseur. Pour cela, nous pouvons utiliser un transformateur appelé applyOffset
.
Les transformateurs sont des fonctions pures qui prennent une valeur et renvoient it-yes-transform. Par exemple: const double = (v) => v * 2
.
const calc, css, pointeur, transformation, valeur = window.popmotion; const applyOffset = transformer;
applyOffset
est une fonction au curry. Cela signifie que lorsque nous l'appelons, cela crée une nouvelle fonction qui peut ensuite recevoir une valeur. Nous appelons d’abord un nombre avec lequel nous voulons mesurer le décalage, dans ce cas la valeur actuelle de action.x
, et un nombre auquel appliquer ce décalage. Dans ce cas, c’est notre sliderX
.
Donc notre applyOffset
la fonction ressemblera à ceci:
const applyPointerMovement = applyOffset (action.x.get (), sliderX.get ());
Nous pouvons maintenant utiliser cette fonction dans le pointeur sortie
callback pour appliquer un mouvement de pointeur au curseur.
action.output ((x) => slider.set (applyPointerMovement (x)));
Le carrousel est maintenant déplaçable au toucher! Vous pouvez tester cela en utilisant une émulation de périphérique dans les outils de développement de Chrome..
Il se sent un peu janky, non? Vous avez peut-être déjà rencontré un défilement semblable à celui-ci: vous levez le doigt et le défilement cesse de fonctionner. Ou le défilement s'arrête net, puis une petite animation prend le relais pour simuler une continuation du défilement.
On ne va pas faire ça. Nous pouvons utiliser l'action physique de Popmotion pour prendre la vitesse réelle de sliderX
et appliquer un frottement sur une durée.
Tout d'abord, ajoutez-le à notre liste sans cesse croissante d'importations:
const calc, css, physique, pointeur, valeur = window.popmotion;
Puis, à la fin de notre stopTouchScroll
fonction, ajouter:
if (action) action.stop (); action = physique (from: sliderX.get (), vélocité: sliderX.getVelocity (), frottement: 0,2) .output ((v) => sliderX.set (v)) .start ();
Ici, de
et rapidité
sont définis avec la valeur actuelle et la vitesse de sliderX
. Cela garantit que notre simulation physique a les mêmes conditions initiales de démarrage que le mouvement de glissement de l'utilisateur.
friction
est défini comme 0,2
. La friction est définie comme une valeur de 0
à 1
, avec 0
être pas de friction du tout et 1
être friction absolue. Essayez de jouer avec cette valeur pour voir le changement que cela apporte au "sentiment" du carrousel lorsqu'un utilisateur cesse de faire glisser.
Un nombre plus petit donnera l'impression d'être plus léger et un nombre plus élevé rendra le mouvement plus lourd. Pour un mouvement de défilement, je me sens 0,2
frappe un bon équilibre entre erratique et lente.
Mais il y a un problème! Si vous avez joué avec votre nouveau carrousel tactile, c'est évident. Nous n'avons pas limité le mouvement, ce qui permet de jeter votre carrousel littéralement!
Il y a un autre transformateur pour ce travail, serrer
. C’est aussi une fonction au curry, ce qui signifie que si on l’appelle avec une valeur minimale et maximale, 0
et 1
, il retournera une nouvelle fonction. Dans cet exemple, la nouvelle fonction limitera le nombre attribué à celle-ci entre 0
et 1
:
pince (0, 1) (5); // retourne 1
Tout d'abord, importer serrer
:
const applyOffset, clamp = transformer;
Nous voulons utiliser cette fonction de serrage sur notre carrousel, alors ajoutez cette ligne après avoir défini minXOffset
:
const clampXOffset = clamp (minXOffset, maxXOffset);
Nous allons modifier les deux sortie
nous avons mis sur nos actions en utilisant une composition fonctionnelle légère avec le tuyau
transformateur.
Lorsque nous appelons une fonction, nous l'écrivons comme ceci:
foo (0);
Si nous voulons donner le résultat de cette fonction à une autre fonction, nous pourrions écrire cela comme ceci:
barre (foo (0));
Cela devient un peu difficile à lire, et cela ne fait qu'empirer à mesure que nous ajoutons de plus en plus de fonctions.
Avec tuyau
, nous pouvons composer une nouvelle fonction sur foo
et bar
que nous pouvons réutiliser:
const foobar = pipe (foo, bar); foobar (0);
C'est également écrit dans un ordre de départ naturel -> ordre d'arrivée, ce qui le rend plus facile à suivre. Nous pouvons l'utiliser pour composer applyOffset
et serrer
en une seule fonction. Importation tuyau
:
const applyOffset, clamp, pipe = transformer;
Remplace le sortie
rappel de notre aiguille
avec:
tuyau ((x) => x, applyOffset (action.x.get (), sliderX.get ()), clampXOffset, (v) => sliderX.set (v))
Et remplace le sortie
rappel de la physique
avec:
pipe (clampXOffset, (v) => sliderX.set (v))
Ce type de composition fonctionnelle peut très facilement créer des processus descriptifs, étape par étape, à partir de fonctions réutilisables plus petites..
Maintenant, quand vous faites glisser le carrousel, il ne bougera pas en dehors de ses limites.
L'arrêt brutal n'est pas très satisfaisant. Mais c'est un problème pour une partie ultérieure!
C'est tout pour la partie 1. Jusqu'à présent, nous avons examiné les carrousels existants pour voir les forces et les faiblesses de différentes approches du défilement. Nous avons utilisé le suivi des entrées et la physique de Popmotion pour animer de manière performante ceux de notre carrousel translateX
avec défilement tactile. Nous avons également été initiés à la composition fonctionnelle et aux fonctions au curry.
Vous pouvez récupérer une version commentée de "l'histoire jusqu'à présent" sur ce CodePen.
Dans les prochains versements, nous examinerons:
Au plaisir de vous y voir!