Dans un précédent article, je vous ai montré comment créer un scénario vertical réactif à partir de zéro. Aujourd’hui, je couvrirai le processus de création des horizontal chronologie.
Comme d'habitude, pour avoir une première idée de ce que nous allons construire, jetez un œil à la démo correspondante de CodePen (consultez la version plus grande pour une meilleure expérience):
Nous avons beaucoup à couvrir, alors commençons!
Le balisage est identique au balisage que nous avons défini pour la chronologie verticale, à l'exception de trois petites choses:
.flèches
) Responsable de la navigation dans la timeline.Voici le balisage requis:
Un peu de contenu ici
L'état initial de la timeline ressemble à ceci:
Après quelques styles de police de base, styles de couleur, etc. que j'ai omis ici par souci de simplicité, nous spécifions quelques règles CSS structurelles:
.timeline white-space: nowrap; débordement-x: caché; .timeline ol taille de la police: 0; largeur: 100vw; rembourrage: 250px 0; transition: tous les 1; .timeline ol li position: relative; affichage: inline-block; type de style de liste: aucun; largeur: 160px; hauteur: 3px; background: #fff; .timeline ol li: last-child width: 280px; .timeline ol li: not (: premier-enfant) margin-left: 14px; .timeline ol li: not (: last-child) :: after content: "; position: absolute; haut: 50%; gauche: calc (100% + 1px); bas: 0; largeur: 12px; hauteur: 12px; transformation: translation (-50%); bordure-rayon: 50%; arrière-plan: # F45B69;
Le plus important ici, vous remarquerez deux choses:
largeur: 100vw
et son parent a overflow-x: caché
. Cela «masque» efficacement les éléments de la liste. Grâce à la navigation dans la timeline, nous pourrons naviguer ultérieurement dans les éléments..Avec ces règles en place, voici l'état actuel de la timeline (sans contenu réel, pour que tout soit clair):
À ce stade, nous appelons la div
éléments (nous les appellerons désormais "éléments de chronologie") qui font partie des éléments de la liste ainsi que leurs ::avant
pseudo-éléments.
De plus, nous utiliserons le : nième enfant (impair)
et : nième enfant (pair)
Pseudo-classes CSS pour différencier les styles des divs pairs et impairs.
Voici les styles courants pour les éléments de scénario:
.ligne de temps ol li div position: absolute; à gauche: calc (100% + 7px); largeur: 280px; rembourrage: 15px; taille de police: 1rem; espace blanc: normal; la couleur noire; fond blanc; .timeline ol li div :: before content: "; position: absolute; top: 100%; left: 0; width: 0; height: 0; border-style: solid;
Puis quelques styles pour les plus étranges:
.ligne de temps ol: nth-child (impair) div top: -16px; transformer: traductionY (-100%); .timeline ol li: nth-child (impair) div :: before top: 100%; largeur de bordure: 8px 8px 0 0; couleur de bordure: blanc transparent transparent transparent;
Et enfin quelques styles pour les paires:
.timeline ol li: nth-child (pair) div top: calc (100% + 16 pixels); .timeline ol li: n-enfant (pair) div :: before top: -8px; largeur de la bordure: 8px 0 0 8px; couleur de bordure: transparent transparent blanc transparent;
Voici le nouvel état de la timeline, avec du contenu ajouté à nouveau:
Comme vous l'avez probablement remarqué, les éléments de la chronologie sont absolument positionnés. Cela signifie qu'ils sont supprimés du flux de documents normal. Dans cet esprit, afin de garantir l’apparition de l’ensemble de la chronologie, nous devons définir des valeurs de remplissage supérieures et inférieures importantes pour la liste. Si nous n'appliquons aucun rembourrage, la chronologie sera recadrée:
Il est maintenant temps de styliser les boutons de navigation. Rappelez-vous que par défaut nous désactivons la flèche précédente et lui donnons la classe de désactivée
.
Voici les styles CSS associés:
.timeline .arrows display: flex; justifier-contenu: centre; marge inférieure: 20 px; .timeline .arrows .arrow__prev margin-right: 20px; .timeline .disabled opacity: .5; .timeline .arrows img width: 45px; hauteur: 45px;
Les règles ci-dessus nous donnent cette chronologie:
La structure de base de la timeline est prête. Ajoutons-y de l'interactivité!
Tout d'abord, nous mettons en place un ensemble de variables que nous utiliserons plus tard.
const timeline = document.querySelector (". timeline ol"), elH = document.querySelectorAll (". timeline li> div"), flèches = document.querySelectorAll (". timeline .arrows .arrow"), arrowPrev = document.querySelector (".timeline .arrows .arrow__prev"), arrowNext = document.querySelector (". timeline .arrows .arrow__next"), firstItem = document.querySelector (". timeline li: premier-enfant"), lastItem = document.querySelector ( ".timeline li: last-child"), xScrolling = 280, disabledClass = "disabled";
Lorsque tous les éléments de la page sont prêts, le init
la fonction s'appelle.
window.addEventListener ("load", init);
Cette fonction déclenche quatre sous-fonctions:
fonction init () setEqualHeights (elH); animateTl (xScrolling, flèches, chronologie); setSwipeFn (timeline, arrowPrev, arrowNext); setKeyboardFn (arrowPrev, arrowNext);
Comme nous le verrons dans un moment, chacune de ces fonctions accomplit une certaine tâche.
Si vous revenez à la dernière démo, vous remarquerez que les éléments de la timeline n'ont pas la même hauteur. Cela n'affecte pas les fonctionnalités principales de notre scénario, mais vous pouvez le préférer si tous les éléments ont la même hauteur. Pour ce faire, nous pouvons leur donner soit une hauteur fixe via CSS (solution facile), soit une hauteur dynamique qui correspond à la hauteur de l'élément le plus haut via JavaScript..
La deuxième option est plus souple et stable, voici donc une fonction qui implémente ce comportement:
fonction setEqualHeights (el) let compteur = 0; pour (soit i = 0; i < el.length; i++) const singleHeight = el[i].offsetHeight; if (counter < singleHeight) counter = singleHeight; for (let i = 0; i < el.length; i++) el[i].style.height = '$counterpx';
Cette fonction récupère la hauteur du plus haut élément de scénario et la définit comme hauteur par défaut pour tous les éléments..
Voici à quoi ressemble la démo:
Passons maintenant à l'animation de la timeline. Nous allons construire la fonction qui implémente ce comportement étape par étape.
Tout d'abord, nous enregistrons un écouteur d'événements de clic pour les boutons de la timeline:
fonction animateTl (scrolling, el, tl) for (let i = 0; i < el.length; i++) el[i].addEventListener("click", function() // code here );
Chaque fois qu'un bouton est cliqué, nous vérifions l'état désactivé des boutons de la timeline et s'ils ne le sont pas, nous les désactivons. Cela garantit que les deux boutons ne seront cliqués qu'une fois jusqu'à la fin de l'animation.
Ainsi, en termes de code, le gestionnaire de clics contient initialement ces lignes:
if (! arrowPrev.disabled) arrowPrev.disabled = true; if (! arrowNext.disabled) arrowNext.disabled = true;
Les prochaines étapes sont les suivantes:
transformer
propriété pour déplacer la timeline 280px vers la droite. La valeur de la xScrolling
variable détermine la quantité de mouvement. transformer
valeur de la ligne de temps et ajoutez ou supprimez à cette valeur la quantité de mouvement souhaitée (c'est-à-dire 280 pixels). Donc, tant que nous cliquons sur le précédent bouton, la valeur de la transformer
propriété diminue et la timeline est déplacée de gauche à droite. Cependant, lorsque le suivant le bouton est cliqué, la valeur du transformer
la propriété augmente et la timeline est déplacée de droite à gauche.Le code qui implémente cette fonctionnalité est le suivant:
laisser compteur = 0; pour (soit i = 0; i < el.length; i++) el[i].addEventListener("click", function() // other code here const sign = (this.classList.contains("arrow__prev")) ? "" : "-"; if (counter === 0) tl.style.transform = 'translateX(-$scrollingpx)'; else const tlStyle = getComputedStyle(tl); // add more browser prefixes if needed here const tlTransform = tlStyle.getPropertyValue("-webkit-transform") || tlStyle.getPropertyValue("transform"); const values = parseInt(tlTransform.split(",")[4]) + parseInt('$sign$scrolling'); tl.style.transform = 'translateX($valuespx)'; counter++; );
Bon travail! Nous venons de définir un moyen d'animer la chronologie. Le prochain défi consiste à déterminer quand cette animation doit s'arrêter. Voici notre approche:
N'oubliez pas que le dernier élément est un élément vide dont la largeur est égale à la largeur des éléments de scénario (c'est-à-dire 280 pixels). Nous lui donnons cette valeur (ou une valeur supérieure) car nous voulons nous assurer que le dernier élément de scénario sera visible avant de désactiver le suivant bouton.
Pour détecter si les éléments cibles sont entièrement visibles ou non dans la fenêtre d'affichage actuelle, nous allons tirer parti du même code que celui utilisé pour la timeline verticale. Le code requis qui provient de ce thread Stack Overflow est le suivant:
fonction isElementInViewport (el) const rect = el.getBoundingClientRect (); return (rect.top> = 0 && rect.left> = 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
Au-delà de la fonction ci-dessus, nous définissons un autre assistant:
fonction setBtnState (el, indicateur = vrai) if (indicateur) el.classList.add (disabledClass); else if (el.classList.contains (disabledClass)) el.classList.remove (disabledClass); el.disabled = false;
Cette fonction ajoute ou supprime le désactivée
classe d'un élément basé sur la valeur de la drapeau
paramètre. De plus, il peut changer l'état désactivé pour cet élément.
Compte tenu de ce que nous avons décrit ci-dessus, voici le code que nous définissons pour vérifier si l'animation doit s'arrêter ou non:
pour (soit i = 0; i < el.length; i++) el[i].addEventListener("click", function() // other code here // code for stopping the animation setTimeout(() => isElementInViewport (firstItem)? setBtnState (arrowPrev): setBtnState (arrowPrev, false); isElementInViewport (lastItem)? setBtnState (arrowNext): setBtnState (arrowNext, false); , 1100); // autre code ici);
Notez qu'il y a un délai de 1,1 seconde avant l'exécution de ce code. Pourquoi cela arrive-t-il?
Si nous revenons à notre CSS, nous verrons cette règle:
.timeline ol transition: tous les 1;
Ainsi, l'animation de la chronologie nécessite 1 seconde. Tant que la procédure est terminée, nous attendons 100 millisecondes, puis nous effectuons nos vérifications..
Voici la chronologie avec des animations:
Jusqu'à présent, la chronologie ne répond pas aux événements tactiles. Ce serait bien si nous pouvions ajouter cette fonctionnalité. Pour ce faire, nous pouvons écrire notre propre implémentation JavaScript ou utiliser l’une des bibliothèques associées (par exemple Hammer.js, TouchSwipe.js) qui existent déjà..
Pour notre démo, nous allons garder cela simple et utiliser Hammer.js, donc d’abord, nous incluons cette bibliothèque dans notre stylo:
Ensuite, nous déclarons la fonction associée:
fonction setSwipeFn (tl, précédente, suivante) const hammer = new Hammer (tl); hammer.on ("swipeleft", () => next.click ()); hammer.on ("swiperight", () => prev.click ());
Dans la fonction ci-dessus, nous procédons comme suit:
swipeleft
et swiperight
événements. La chronologie avec prise en charge du balayage:
Améliorons encore l'expérience utilisateur en fournissant un support pour la navigation au clavier. Nos buts:
La fonction associée est la suivante:
fonction setKeyboardFn (prev, next) document.addEventListener ("keydown", (e) => if ((e.which === 37) || (e.which === 39)) const timelineOfTop = timeline .offsetTop; const y = window.pageYOffset; if (timelineOfTop! == y) window.scrollTo (0, timelineOfTop); if (e.which === 37) prev.click (); sinon if ( e.which === 39) next.click (););
La timeline avec support du clavier:
Nous avons presque terminé! Dernier point mais non le moindre, faisons en sorte que le calendrier soit réactif. Lorsque la fenêtre d’affichage mesure moins de 600 pixels, la disposition empilée suivante doit apparaître:
Comme nous utilisons une approche "bureau d'abord", voici les règles CSS que nous devons écraser:
Écran @média et (largeur maximale: 599 pixels) . olimine., olimine. ol li largeur: auto; .timeline ol padding: 0; transformer: aucun! important; .timeline ol li display: block; hauteur: auto; fond: transparent; .timeline ol li: premier-enfant margin-top: 25px; .timeline ol li: not (: premier-enfant) margin-left: auto; .timeline ol li div width: 94%; hauteur: auto! important; marge: 0 auto 25 px; .timeline ol li: nth-child div position: static; .timeline ol li: nième enfant (impair) div transform: none; .timeline ol: nth-child (impair) div :: before,. timeline ol: nth-child (pair) div :: before Top 100%; transformer: translateX (-50%); bordure: aucune; bordure gauche: 1px blanc solide; hauteur: 25px; .timeline avec li: last-child, .timeline avec li: nth-last-child (2) div :: before,. : aucun;
Remarque: Pour deux des règles ci-dessus, nous avons dû utiliser le !important
règle pour remplacer les styles en ligne associés appliqués via JavaScript.
L'état final de notre chronologie:
La démo fonctionne bien dans tous les navigateurs et appareils récents. En outre, comme vous l'avez peut-être remarqué, nous utilisons Babel pour compiler notre code ES6 jusqu'à ES5..
Le seul petit problème que j'ai rencontré lors du test est la modification du rendu du texte qui survient lorsque la timeline est animée. Bien que j'aie essayé diverses approches proposées dans différents threads Stack Overflow, je n'ai pas trouvé de solution simple pour tous les systèmes d'exploitation et tous les navigateurs. Alors, gardez à l'esprit que vous pourriez rencontrer de petits problèmes de rendu des polices lorsque la timeline est animée..
Dans ce tutoriel assez important, nous avons commencé avec une simple liste ordonnée et créé un scénario horizontal réactif. Sans doute, nous avons couvert beaucoup de choses intéressantes, mais j'espère que vous avez aimé travailler pour le résultat final et que cela vous a aidé à acquérir de nouvelles connaissances..
Si vous avez des questions ou s'il y a quelque chose que vous n'avez pas compris, faites le moi savoir dans les commentaires ci-dessous!
Si vous souhaitez améliorer ou prolonger ce délai, voici quelques solutions: