Créer le carrousel parfait, partie 3

Ceci est la troisième et dernière partie de notre série de didacticiels «Créer le carrousel parfait». Dans la première partie, nous avons évalué les carrousels sur Netflix et Amazon, deux des carrousels les plus utilisés au monde. Nous avons mis en place notre carrousel et mis en œuvre le défilement tactile.

Ensuite, dans la partie 2, nous avons ajouté le défilement horizontal de la souris, la pagination et un indicateur de progression. Boom.

Dans notre dernière partie, nous allons nous intéresser au monde trouble et souvent oublié de l’accessibilité du clavier. Nous allons ajuster notre code pour réévaluer le carrousel lorsque la taille de la fenêtre d'affichage change. Et pour finir, quelques retouches à la physique du printemps.

Vous pouvez reprendre où nous sommes partis avec ce CodePen.

Accessibilité du clavier

Il est vrai que la majorité des utilisateurs ne se fient pas à la navigation au clavier. Malheureusement, nous oublions parfois les utilisateurs qui le font. Dans certains pays, laisser un site Web inaccessible peut être illégal. Mais pire, c'est un coup de bite.

La bonne nouvelle est qu’il est généralement facile à mettre en œuvre! En fait, les navigateurs font la majorité du travail pour nous. Sérieusement: essayez de parcourir le carrousel que nous avons créé. Parce que nous avons utilisé le balisage sémantique, vous pouvez déjà!

Sauf que vous remarquerez que nos boutons de navigation disparaissent. En effet, le navigateur ne permet pas de se concentrer sur un élément situé en dehors de notre fenêtre de visualisation. Donc même si nous avons débordement caché ensemble, nous ne pouvons pas faire défiler la page horizontalement; sinon, la page défilera pour afficher l'élément en surbrillance.

C’est bien, et à mon avis, cela serait considéré comme "utilisable", mais pas vraiment délicieux.

Le carrousel de Netflix fonctionne également de cette manière. Mais comme la majorité de leurs titres sont chargés paresseux et qu'ils sont également accessibles de manière passive au clavier (ce qui signifie qu'ils n'ont pas écrit de code spécifique pour le manipuler), nous ne pouvons en réalité sélectionner aucun titre au-delà du peu que nous avons déjà chargé. Cela a aussi l'air terrible: 

On peut faire mieux.

Manipuler le concentrer un événement

Pour ce faire, nous allons écouter le concentrer événement qui tire sur n'importe quel élément du carrousel. Lorsqu'un élément reçoit le focus, nous allons l'interroger pour connaître sa position. Ensuite, nous vérifierons cela par rapport à sliderX et sliderVisibleWidth pour voir si cet élément est dans la fenêtre visible. Si ce n'est pas le cas, nous allons y paginer en utilisant le même code que celui écrit dans la partie 2.

À la fin de carrousel fonction, ajoutez cet écouteur d'événement:

slider.addEventListener ('focus', onFocus, true);

Vous remarquerez que nous avons fourni un troisième paramètre, vrai. Plutôt que d'ajouter un écouteur d'événement à chaque élément, nous pouvons utiliser ce que l'on appelle la délégation d'événements pour écouter des événements sur un seul élément, leur parent direct. le concentrer l'événement ne bouillonne pas, donc vrai dit à l'auditeur d'événement d'écouter le Capturer stade, le stade où l'événement se déclenche sur tous les éléments de la la fenêtre jusqu'à la cible (dans ce cas, l'élément recevant le focus).

Au-dessus de notre groupe croissant d’auditeurs d’événements, ajoutez le onFocus une fonction:

fonction onFocus (e) 

Nous allons travailler dans cette fonction pour le reste de cette section.

Nous devons mesurer l'élément la gauche et droite décaler et vérifier si l'un ou l'autre point se situe en dehors de la zone actuellement visible.

L'article est fourni par l'événement cible paramètre, et nous pouvons le mesurer avec getBoundingClientRect

const left, right = e.target.getBoundingClientRect ();

la gauche et droite sont par rapport à la fenêtre d'affichage, pas le curseur. Nous devons donc obtenir le conteneur du carrousel la gauche compenser pour tenir compte de cela. Dans notre exemple, ce sera 0, mais pour rendre le carrousel robuste, il devrait être placé n'importe où.

const carouselLeft = container.getBoundingClientRect (). left;

Maintenant, nous pouvons faire une simple vérification pour voir si l'élément est en dehors de la zone visible du curseur et paginer dans cette direction:

si (à gauche < carouselLeft)  gotoPrev();  else if (right > carrouselLeft + sliderVisibleWidth) gotoNext (); 

Maintenant, lorsque nous discutons, le carrousel pagine en toute confiance avec notre focus clavier! Quelques lignes de code pour montrer plus d'amour à nos utilisateurs.

Réévaluer le carrousel

Vous avez peut-être remarqué, au cours de ce tutoriel, que si vous redimensionnez la fenêtre de votre navigateur, le carrousel ne paginera plus correctement. En effet, nous n’avons mesuré sa largeur par rapport à sa surface visible qu’une seule fois, au moment de l’initialisation..

Pour que notre carrousel se comporte correctement, nous devons remplacer une partie de notre code de mesure par un nouvel écouteur d’événements qui se déclenche lorsque le la fenêtre redimensionne.

Maintenant, vers le début de votre carrousel fonction, juste après la ligne où nous définissons barre de progression, nous voulons remplacer trois de ces const mesures avec laisser, parce que nous allons les changer lorsque la fenêtre d'affichage change:

const totalItemsWidth = getTotalItemsWidth (items); const maxXOffset = 0; laissez minXOffset = 0; laissez sliderVisibleWidth = 0; laissez clampXOffset;

Ensuite, nous pouvons déplacer la logique qui a précédemment calculé ces valeurs vers un nouveau mesureCarousel une fonction:

function measureCarousel () sliderVisibleWidth = slider.offsetWidth; minXOffset = - (totalItemsWidth - sliderVisibleWidth); clampXOffset = clamp (minXOffset, maxXOffset); 

Nous souhaitons appeler immédiatement cette fonction afin de toujours définir ces valeurs lors de l'initialisation. Sur la ligne suivante, appelez mesureCarousel:

mesureCarousel ();

Le carrousel devrait fonctionner exactement comme avant. Pour mettre à jour le redimensionnement de la fenêtre, nous ajoutons simplement cet écouteur d'événement à la toute fin de notre carrousel une fonction:

window.addEventListener ('resize', measureCarousel);

Maintenant, si vous redimensionnez le carrousel et essayez de paginer, cela continuera à fonctionner comme prévu.

Une note sur les performances

Cela vaut la peine de considérer que dans le monde réel, vous pouvez avoir plusieurs carrousels sur la même page, ce qui multiplie l'impact sur la performance de ce code de mesure par ce montant..

Comme nous l'avons brièvement expliqué dans la partie 2, il est déconseillé d'effectuer des calculs lourds plus souvent que nécessaire. Avec les événements de pointeur et de défilement, nous avons dit que vous souhaitiez les effectuer une fois par image pour maintenir la cadence de 60 images par seconde. Les événements de redimensionnement sont un peu différents en ce sens que tout le document sera refondu, probablement le moment le plus gourmand en ressources d'une page Web..

Nous n'avons pas besoin de réévaluer le carrousel jusqu'à ce que l'utilisateur ait fini de redimensionner la fenêtre, car ils n'interagiront pas entre-temps. Nous pouvons envelopper notre mesureCarousel fonctionner dans une fonction spéciale appelée rebondir.

Une fonction anti-rebond dit essentiellement: "N'activez cette fonction que si elle n'a pas été appelée X millisecondes. "Vous pouvez en savoir plus sur l'excellent apprêt de David Walsh et trouver un exemple de code aussi.

La touche finale

Jusqu'ici, nous avons créé un très bon manège. Il est accessible, il s’anime agréablement, il fonctionne avec le toucher et la souris, et il offre une grande souplesse de conception, ce qui empêche les carrousels de défiler de manière native..

Mais ce n’est pas la série de didacticiels "Créer un très bon carrousel". Il est temps pour nous de montrer un peu, et de le faire, nous avons une arme secrète. Ressorts.

Nous allons ajouter deux interactions utilisant des ressorts. Un pour le toucher et un pour la pagination. Ils vont tous les deux faire savoir à l'utilisateur, de manière amusante et ludique, qu'ils ont atteint la fin du carrousel..

Printemps tactile

Premièrement, ajoutons un remorqueur de style iOS lorsqu'un utilisateur essaie de faire défiler le curseur au-delà de ses limites. Actuellement, nous limitons le défilement tactile avec clampXOffset. Remplaçons plutôt ceci par un code qui applique un remorqueur lorsque le décalage calculé est en dehors de ses limites..

Premièrement, nous devons importer notre printemps. Il y a un transformateur appelé nonlinéaire qui applique une force croissante de façon exponentielle contre le nombre que nous lui fournissons, vers un origine. Ce qui signifie que plus nous tirons le curseur, plus il se soulève. Nous pouvons l'importer comme ceci:

const applyOffset, clamp, nonlinearSpring, pipe = transformer;

dans le DetermineDragDirection fonction, nous avons ce code:

action.output (pipe ((x) => x, applyOffset (action.x.get (), sliderX.get ()), clampXOffset, (v) => sliderX.set (v));

Juste au-dessus, créons nos deux ressorts, un pour chaque limite de défilement du carrousel:

élasticité constante = 5; const tugLeft = nonlinearSpring (élasticité, maxXOffset); const tugRight = nonlinearSpring (élasticité, minXOffset);

Choisir une valeur pour élasticité est une question de jouer et de voir ce qui se sent bien. Un nombre trop bas et le ressort est trop raide. Trop haut et vous ne remarquerez pas son tirant, ou pire, il poussera le curseur encore plus loin du doigt de l'utilisateur!

Maintenant, il suffit d’écrire une fonction simple qui appliquera l’un de ces ressorts si la valeur fournie est en dehors de la plage autorisée:

const applySpring = (v) => if (v> maxXOffset) renvoie tugLeft (v); si (v < minXOffset) return tugRight(v); return v; ;

Nous pouvons remplacer clampXOffset dans le code ci-dessus avec appliquerSpring. Maintenant, si vous tirez le curseur au-delà de ses limites, il va tirer en arrière!

Cependant, lorsque nous lâchons le printemps, il se remet en place sans cérémonie. Nous voulons modifier notre stopTouchScroll qui gère actuellement le défilement d’élan, pour vérifier si le curseur se trouve toujours en dehors de la plage autorisée et, le cas échéant, appliquer un ressort avec le bouton la physique action à la place.

Physique de printemps

le la physique action est également capable de modéliser les ressorts. Nous devons juste lui fournir printemps et à Propriétés.

Dans stopTouchScroll, déplacer le parchemin existant la physique initialisation à un morceau de logique qui garantit que nous sommes dans les limites de défilement:

const currentX = sliderX.get (); si (currentX < minXOffset || currentX > maxXOffset)  else action = physique (from: currentX, vitesse: sliderX.getVelocity (), friction: 0,2). output (pipe (clampXOffset, (v) => sliderX.set (v))). (); 

Dans la première clause de la si déclaration, nous savons que le curseur est en dehors des limites de défilement, nous pouvons donc ajouter notre ressort:

action = physique (from: currentX, to: (currentX < minXOffset) ? minXOffset : maxXOffset, spring: 800, friction: 0.92 ).output((v) => sliderX.set (v)) .start ();

Nous voulons créer un ressort qui se sent vif et réactif. J'ai choisi un niveau relativement élevé printemps valeur d'avoir un "pop" serré, et j'ai baissé le friction à 0,92 pour permettre un petit rebond. Vous pouvez définir ceci pour 1 éliminer complètement le rebond.

Comme un peu de devoirs, essayez de remplacer le clampXOffset dans le sortie fonction du parchemin la physique avec une fonction qui déclenche un ressort similaire lorsque le décalage x atteint ses limites. Plutôt que l’arrêt brusque actuel, essayez de le faire rebondir doucement à la fin.

Printemps pagination

Les utilisateurs de Touch ont toujours la bonté du printemps, non? Partageons cet amour pour les utilisateurs d'ordinateurs de bureau en détectant quand le carrousel est à la limite de son défilement, et en ayant un remorqueur indicatif pour montrer clairement et en toute confiance à l'utilisateur qu'il est au bout..

Premièrement, nous voulons désactiver les boutons de pagination lorsque la limite est atteinte. Ajoutons d'abord une règle CSS qui style les boutons pour montrer qu'ils sont désactivée. dans le bouton règle, ajouter:

transition: fond 200ms linéaire; & .disabled background: #eee; 

Nous utilisons une classe ici au lieu de la plus sémantique désactivée attribut parce que nous voulons toujours capturer les événements de clic, qui, comme son nom l'indique, désactivée bloquerait.

Ajoute ça désactivée classe au bouton Précédent, car chaque carrousel commence sa vie avec un 0 décalage:

Vers le haut de carrousel, faire une nouvelle fonction appelée checkNavButtonStatus. Nous voulons que cette fonction vérifie simplement la valeur fournie par rapport à minXOffset et maxXOffset et mettre le bouton désactivée classe en conséquence:

fonction 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'); 

Il serait tentant d'appeler cela à chaque fois sliderX changements. Si nous le faisions, les boutons se mettraient à clignoter chaque fois qu'un ressort oscillait autour des limites du défilement. Cela conduirait aussi à bizarre comportement si l’un des boutons était enfoncé pendant l’une de ces animations printanières. Le remorqueur "Scroll end" devrait toujours tirer si nous sommes à la fin du carrousel, même s'il y a une animation printanière qui l'éloigne de l'extrémité absolue.

Nous devons donc être plus sélectifs quant au moment d'appeler cette fonction. Il semble judicieux de l'appeler:

Sur la dernière ligne du sur roue, ajouter checkNavButtonStatus (newX);.

Sur la dernière ligne de aller à, ajouter checkNavButtonStatus (targetX);.

Et enfin, à la fin de DetermineDragDirection, et dans la clause de défilement de momentum (le code dans la autre) de stopTouchScroll, remplacer:

(v) => sliderX.set (v)

Avec:

(v) => sliderX.set (v); checkNavButtonStatus (v); 

Il ne reste plus qu'à modifier gotoPrev et gotoNext pour vérifier la classList de leur bouton de déclenchement pour désactivée et ne paginez que s'il est absent:

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);

le notifierfin la fonction est juste une autre la physique printemps, et il ressemble à ceci:

fonction notifyEnd (delta, targetOffset) if (action) action.stop (); action = physique (de: sliderX.get (), à: targetOffset, vitesse: 2000 * delta, ressort: 300, frottement: 0.9) .output ((v) => sliderX.set (v)) .start ( ) 

Avoir un jeu avec cela, et encore, ajuster le la physique params à votre goût.

Il ne reste qu'un petit bug. Lorsque le curseur dépasse sa limite la plus à gauche, la barre de progression est inversée. Nous pouvons rapidement résoudre ce problème en remplaçant:

progressBarRenderer.set ('scaleX', progress);

Avec:

progressBarRenderer.set ('scaleX', Math.max (progress, 0));

nous pourraitl'empêcher de rebondir dans l'autre sens, mais personnellement, je pense que c'est plutôt cool que cela reflète le mouvement du printemps. Ça a l'air bizarre quand ça tourne à l'envers.

Nettoyer après vous-même

Avec les applications d'une seule page, les sites Web durent plus longtemps dans la session d'un utilisateur. Souvent, même lorsque la "page" change, nous exécutons toujours le même runtime JS que lors du chargement initial. Nous ne pouvons pas compter sur une table rase à chaque fois que l'utilisateur clique sur un lien. Cela signifie que nous devons nettoyer nous-mêmes pour éviter que les auditeurs d'événements tirent sur des éléments morts..

Dans React, ce code est placé dans le composantWillLeave méthode. Vue utilise beforeDestroy. Ceci est une implémentation JS pure, mais nous pouvons toujours fournir une méthode de destruction qui fonctionnerait également dans les deux cas..

Jusqu'ici, notre carrousel la fonction n'a rien retourné. Changeons ça.

Tout d'abord, changez la dernière ligne, la ligne qui appelle carrousel, à:

const destroyCarousel = carrousel (document.querySelector ('. conteneur'));

Nous allons revenir juste une chose de carrousel, une fonction qui dissocie tous nos auditeurs d'événements. Tout à la fin de la carrousel fonction, écris:

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); ;

Maintenant, si vous appelez détruireCarousel et essayez de jouer avec le carrousel, rien ne se passe! C'est presque un peu triste de voir ça comme ça.

Et c'est ça

Ouf. C'était beaucoup! Jusqu'où nous sommes venus. Vous pouvez voir le produit fini à ce CodePen. Dans cette dernière partie, nous avons ajouté l’accessibilité au clavier, le réévaluation du carrousel lorsque la fenêtre change, des ajouts amusants avec la physique du printemps et l’étape déchirante mais nécessaire de tout détruire à nouveau..

J'espère que vous avez apprécié ce tutoriel autant que j'ai aimé l'écrire. J'aimerais connaître votre avis sur les moyens d'améliorer encore l'accessibilité ou d'ajouter des petites touches amusantes..