Créer un composant "Tabs"

Présentation de base de la création d'un composant d'onglets semblable à ceux que l'on trouve dans les applications iOS et Android.

Dans cet article, je souhaite partager ma réflexion sur la création d'un composant d'onglets pour le Web, qui est réactif, prend en charge plusieurs entrées d'appareils et fonctionne sur tous les navigateurs. Essayez la démonstration.

Démo

Si vous préférez les vidéos, voici une version YouTube de cet article :

Présentation

Les onglets sont un composant courant des systèmes de conception, mais ils peuvent prendre de nombreuses formes. Il y a d'abord eu les onglets pour ordinateur de bureau basés sur l'élément <frame>, et maintenant nous avons des composants mobiles fluides qui animent le contenu en fonction des propriétés physiques. Ils essaient tous de faire la même chose : économiser de l'espace.

Aujourd'hui, l'essentiel de l'expérience utilisateur des onglets est une zone de navigation par bouton qui active ou désactive la visibilité du contenu dans un cadre d'affichage. De nombreuses zones de contenu différentes partagent le même espace, mais sont présentées de manière conditionnelle en fonction du bouton sélectionné dans la navigation.

Le collage est assez chaotique en raison de la grande diversité de styles que le Web a appliqués au concept de composant.
Montage de styles de conception Web de composants d'onglets au cours des 10 dernières années

Tactiques Web

Dans l'ensemble, j'ai trouvé ce composant assez simple à créer, grâce à quelques fonctionnalités essentielles de la plate-forme Web :

  • scroll-snap-points pour des interactions élégantes avec le balayage et le clavier, avec des positions d'arrêt de défilement appropriées
  • Liens profonds via des hachages d'URL pour la prise en charge de l'ancrage et du partage de défilement sur la page gérés par le navigateur
  • Compatibilité avec les lecteurs d'écran grâce au balisage des éléments <a> et id="#hash"
  • prefers-reduced-motion pour activer les transitions de fondu enchaîné et le défilement instantané sur la page
  • Fonctionnalité Web @scroll-timeline en cours de rédaction pour souligner dynamiquement l'onglet sélectionné et en modifier la couleur

Le code HTML

Fondamentalement, l'UX ici est la suivante : cliquer sur un lien, faire en sorte que l'URL représente l'état de la page imbriquée, puis voir la zone de contenu se mettre à jour lorsque le navigateur fait défiler la page jusqu'à l'élément correspondant.

Il existe des membres de contenu structurel : des liens et des :target. Nous avons besoin d'une liste de liens, pour laquelle un <nav> est idéal, et d'une liste d'éléments <article>, pour laquelle un <section> est idéal. Chaque hachage de lien correspond à une section, ce qui permet au navigateur de faire défiler les éléments à l'aide de l'ancrage.

Un bouton de lien est cliqué, ce qui fait glisser le contenu sélectionné.

Par exemple, en cliquant sur un lien, l'article :target est automatiquement sélectionné dans Chrome 89, sans avoir besoin de JS. L'utilisateur peut ensuite faire défiler le contenu de l'article avec son périphérique d'entrée comme d'habitude. Il s'agit d'un contenu sans frais, comme indiqué dans le balisage.

J'ai utilisé le balisage suivant pour organiser les onglets :

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

Je peux établir des connexions entre les éléments <a> et <article> avec les propriétés href et id comme suit :

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

J'ai ensuite rempli les articles avec des quantités variables de lorem et les liens avec des titres de longueur et d'ensemble d'images variables. Maintenant que nous avons du contenu, nous pouvons commencer la mise en page.

Mises en page à défilement

Ce composant comporte trois types de zones de défilement :

  • La navigation (en rose) est défilable horizontalement.
  • La zone de contenu (en bleu) est défilable horizontalement.
  • Chaque élément d'article (vert) peut être défilé verticalement.
Trois boîtes colorées avec des flèches directionnelles de couleur assortie qui délimitent les zones de défilement et indiquent le sens du défilement.

Il existe deux types d'éléments impliqués dans le défilement :

  1. Fenêtre
    Boîte aux dimensions définies avec le style de propriété overflow.
  2. Surface surdimensionnée
     Dans cette mise en page, il s'agit des conteneurs de liste : liens de navigation, articles de section et contenu des articles.

Mise en page <snap-tabs>

La mise en page de premier niveau que j'ai choisie était "flex" (Flexbox). J'ai défini la direction sur column, de sorte que l'en-tête et la section sont ordonnés verticalement. Il s'agit de notre première fenêtre de défilement, qui masque tout ce qui dépasse. L'en-tête et la section utiliseront bientôt le défilement hors limites, en tant que zones individuelles.

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

Revenons au diagramme coloré à trois niveaux :

  • <header> est maintenant prêt à devenir le conteneur de défilement (rose).
  • <section> est prêt à devenir le conteneur de défilement (bleu).

Les cadres que j'ai mis en évidence ci-dessous avec VisBug nous aident à voir les fenêtres créées par les conteneurs de défilement.

Les éléments d&#39;en-tête et de section sont recouverts d&#39;un calque rose vif qui délimite l&#39;espace qu&#39;ils occupent dans le composant.

Mise en page des onglets <header>

La mise en page suivante est presque identique : j'utilise flex pour créer un ordre vertical.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

Le .snap-indicator doit se déplacer horizontalement avec le groupe de liens. Cette mise en page d'en-tête permet de préparer cette étape. Aucun élément positionné de manière absolue ici !

Les éléments &quot;nav&quot; et &quot;span.indicator&quot; sont recouverts d&#39;un calque rose vif qui délimite l&#39;espace qu&#39;ils occupent dans le composant.

Ensuite, les styles de défilement. Il s'avère que nous pouvons partager les styles de défilement entre nos deux zones de défilement horizontal (en-tête et section). J'ai donc créé une classe utilitaire, .scroll-snap-x.

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

Chacun d'eux a besoin d'un débordement sur l'axe X, d'un confinement de défilement pour piéger le défilement excessif, de barres de défilement masquées pour les appareils tactiles et, enfin, d'un alignement sur les points de défilement pour verrouiller les zones de présentation du contenu. L'ordre de tabulation du clavier est accessible et toutes les interactions guident naturellement la sélection. Les conteneurs d'accrochage de défilement bénéficient également d'une interaction de style carrousel agréable depuis leur clavier.

Mise en page de l'en-tête des onglets <nav>

Les liens de navigation doivent être disposés sur une ligne, sans saut de ligne, centrés verticalement, et chaque élément de lien doit s'aligner sur le conteneur scroll-snap. Swift work for 2021 CSS!

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

Chaque lien définit son propre style et sa propre taille. La mise en page de la navigation n'a donc besoin de spécifier que la direction et le flux. Les largeurs uniques des éléments de navigation rendent la transition entre les onglets amusante, car l'indicateur ajuste sa largeur à la nouvelle cible. En fonction du nombre d'éléments présents, le navigateur affichera ou non une barre de défilement.

Les éléments &quot;a&quot; de la navigation sont recouverts d&#39;un calque rose vif qui délimite l&#39;espace qu&#39;ils occupent dans le composant et l&#39;endroit où ils débordent.

Mise en page des onglets <section>

Cette section est un élément flexible et doit être le principal consommateur d'espace. Il doit également créer des colonnes dans lesquelles les articles seront placés. Encore une fois, bravo pour votre travail rapide pour CSS 2021 ! block-size: 100% étire cet élément pour qu'il remplisse le parent autant que possible. Ensuite, pour sa propre mise en page, il crée une série de colonnes dont la largeur est 100% celle du parent. Les pourcentages fonctionnent très bien ici, car nous avons défini des contraintes fortes sur le parent.

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

C'est comme si nous disions "développe-toi verticalement autant que possible, de manière insistante" (rappelez-vous que nous avons défini l'en-tête sur flex-shrink: 0 : c'est une défense contre cette poussée d'expansion), ce qui définit la hauteur de ligne pour un ensemble de colonnes de hauteur totale. Le style auto-flow indique à la grille de toujours disposer les éléments enfants sur une ligne horizontale, sans retour à la ligne, exactement ce que nous voulons, pour qu'ils débordent de la fenêtre parente.

Les éléments de l&#39;article sont recouverts d&#39;un calque rose vif qui délimite l&#39;espace qu&#39;ils occupent dans le composant et l&#39;endroit où ils débordent.

J'ai parfois du mal à m'y retrouver ! Cet élément de section s'insère dans une boîte, mais a également créé un ensemble de boîtes. J'espère que les éléments visuels et les explications vous sont utiles.

Mise en page des onglets <article>

L'utilisateur doit pouvoir faire défiler le contenu de l'article, et les barres de défilement ne doivent s'afficher qu'en cas de dépassement. Ces éléments d'article sont bien positionnés. Il est à la fois un parent de défilement et un enfant de défilement. Le navigateur gère ici pour nous certaines interactions tactiles, de souris et de clavier délicates.

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

J'ai choisi d'ancrer les articles dans leur scroller parent. J'aime beaucoup la façon dont les éléments de lien de navigation et les éléments d'article s'alignent sur le début de ligne de leurs conteneurs de défilement respectifs. Elle ressemble à une relation harmonieuse.

L&#39;élément &quot;article&quot; et ses éléments enfants sont recouverts d&#39;un calque rose vif, qui délimite l&#39;espace qu&#39;ils occupent dans le composant et la direction dans laquelle ils débordent.

L'article est un enfant de la grille et sa taille est prédéterminée pour correspondre à la zone de la fenêtre d'affichage pour laquelle nous voulons fournir une expérience de défilement. Cela signifie que je n'ai pas besoin de styles de hauteur ou de largeur ici, je dois juste définir comment il déborde. J'ai défini overflow-y sur "auto", puis j'ai également piégé les interactions de défilement avec la propriété overscroll-behavior.

Récapitulatif des trois zones de défilement

Ci-dessous, j'ai choisi dans les paramètres système de "toujours afficher les barres de défilement". Je pense qu'il est doublement important que la mise en page fonctionne avec ce paramètre activé, car cela me permet d'examiner la mise en page et l'orchestration du défilement.

Les trois barres de défilement sont définies sur &quot;Afficher&quot;, ce qui consomme désormais de l&#39;espace de mise en page, mais notre composant est toujours aussi beau.

Je pense que la gouttière de la barre de défilement de ce composant permet de montrer clairement où se trouvent les zones de défilement, la direction qu'elles prennent en charge et la façon dont elles interagissent les unes avec les autres. Réfléchissez à la façon dont chacun de ces cadres de fenêtre de défilement est également un parent flex ou grille d'une mise en page.

Les outils de développement peuvent nous aider à visualiser cela :

Les zones de défilement sont dotées de calques d&#39;outils de grille et de boîte flexible, qui indiquent l&#39;espace qu&#39;elles occupent dans le composant et la direction dans laquelle elles débordent.
Outils pour les développeurs Chromium affichant la mise en page de l'élément de navigation flexbox rempli d'éléments d'ancrage, la mise en page de la section de grille remplie d'éléments d'article et les éléments d'article remplis de paragraphes et d'un élément d'en-tête.

Les mises en page à défilement sont complètes : elles sont ancrables, accessibles par lien profond et accessibles au clavier. Base solide pour les améliorations de l'UX, du style et de l'expérience utilisateur.

Fonctionnalité mise en avant

Les enfants ancrés au défilement conservent leur position verrouillée lors du redimensionnement. Cela signifie que JavaScript n'aura pas besoin d'afficher quoi que ce soit lors de la rotation de l'appareil ou du redimensionnement du navigateur. Essayez-le dans le mode Appareil des outils pour les développeurs Chromium en sélectionnant un mode autre que Réactif, puis en redimensionnant le cadre de l'appareil. Notez que l'élément reste visible et verrouillé avec son contenu. Cette fonctionnalité est disponible depuis que Chromium a mis à jour son implémentation pour correspondre à la spécification. Pour en savoir plus, consultez cet article de blog.

Animation

L'objectif de l'animation est d'établir un lien clair entre les interactions et le retour d'informations de l'UI. Cela permet de guider ou d'aider l'utilisateur à découvrir tout le contenu de manière (espérons-le) fluide. Je vais ajouter du mouvement avec intention et de manière conditionnelle. Les utilisateurs peuvent désormais spécifier leurs préférences de mouvement dans leur système d'exploitation, et j'aime beaucoup répondre à leurs préférences dans mes interfaces.

Je vais associer un soulignement d'onglet à la position de défilement de l'article. L'accrochage ne sert pas seulement à aligner les éléments, mais aussi à ancrer le début et la fin d'une animation. Le <nav>, qui fait office de mini-carte, reste ainsi connecté au contenu. Nous vérifierons la préférence de mouvement de l'utilisateur à partir de CSS et de JS. Il existe plusieurs endroits où vous pouvez faire preuve de considération.

Comportement de défilement

Vous pouvez améliorer le comportement de mouvement de :target et de element.scrollIntoView(). Par défaut, elle est instantanée. Le navigateur définit simplement la position de défilement. Et si nous voulions passer à cette position de défilement au lieu d'y clignoter ?

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

Étant donné que nous introduisons ici un mouvement que l'utilisateur ne contrôle pas (comme le défilement), nous n'appliquons ce style que si l'utilisateur n'a aucune préférence dans son système d'exploitation concernant la réduction du mouvement. De cette façon, nous n'introduisons le défilement que pour les personnes qui l'acceptent.

Indicateur d'onglets

L'objectif de cette animation est d'aider à associer l'indicateur à l'état du contenu. J'ai décidé d'utiliser des styles de fondu enchaîné des couleurs border-bottom pour les utilisateurs qui préfèrent les mouvements réduits, et une animation de fondu enchaîné des couleurs et de déplacement lié au défilement pour les utilisateurs qui acceptent les mouvements.

Dans les outils de développement Chromium, je peux activer/désactiver la préférence et montrer les deux styles de transition différents. J'ai beaucoup aimé créer cette application.

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

Je masque .snap-indicator lorsque l'utilisateur préfère une animation réduite, car je n'en ai plus besoin. Je le remplace ensuite par des styles border-block-end et un transition. Notez également que, dans l'interaction des onglets, l'élément de navigation actif n'est pas seulement mis en évidence par un soulignement de la marque, mais que la couleur de son texte est également plus foncée. L'élément actif présente un contraste de couleur de texte plus élevé et un accent lumineux en dessous.

Quelques lignes de CSS supplémentaires suffisent à ce qu'une personne se sente prise en compte (dans le sens où nous respectons attentivement ses préférences de mouvement). J'adore.

@scroll-timeline

Dans la section ci-dessus, je vous ai montré comment gérer les styles de fondu enchaîné pour le mouvement réduit. Dans cette section, je vais vous montrer comment j'ai lié l'indicateur et une zone de défilement. Nous allons maintenant vous présenter des fonctionnalités expérimentales amusantes. J'espère que vous êtes aussi enthousiaste que moi.

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);

Je vérifie d'abord la préférence de mouvement de l'utilisateur à partir de JavaScript. Si le résultat est false, ce qui signifie que l'utilisateur préfère les mouvements réduits, nous n'exécuterons aucun des effets de mouvement d'association de défilement.

if (motionOK) {
  // motion based animation code
}

Au moment de la rédaction de ce document, @scroll-timeline n'est compatible avec aucun navigateur. Il s'agit d'une spécification provisoire avec uniquement des implémentations expérimentales. Il possède toutefois un polyfill que j'utilise dans cette démo.

ScrollTimeline

Bien que CSS et JavaScript puissent tous deux créer des timelines de défilement, j'ai opté pour JavaScript afin de pouvoir utiliser des mesures d'éléments en direct dans l'animation.

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

Je veux qu'un élément suive la position de défilement d'un autre. En créant un ScrollTimeline, je définis le pilote du lien de défilement, le scrollSource. Normalement, une animation sur le Web s'exécute par rapport à un tick de chronologie global, mais avec un sectionScrollTimeline personnalisé en mémoire, je peux changer tout cela.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Avant de parler des images clés de l'animation, je pense qu'il est important de souligner que le follower de défilement, tabindicator, sera animé en fonction d'une timeline personnalisée, à savoir le défilement de notre section. La liaison est maintenant terminée, mais il manque l'ingrédient final : les points avec état entre lesquels l'animation doit se faire, également appelés "keyframes".

Images clés dynamiques

Il existe une méthode CSS déclarative pure très efficace pour animer avec @scroll-timeline, mais l'animation que j'ai choisie était trop dynamique. Il n'existe aucun moyen de passer d'une largeur auto à une autre, ni de créer dynamiquement un nombre d'images clés en fonction de la longueur des enfants.

JavaScript sait comment obtenir ces informations. Nous allons donc itérer sur les enfants nous-mêmes et récupérer les valeurs calculées au moment de l'exécution :

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Pour chaque tabnavitem, déstructurez la position offsetLeft et renvoyez une chaîne qui l'utilise comme valeur translateX. Cela crée quatre images clés de transformation pour l'animation. La même chose est faite pour la largeur : chaque élément est interrogé sur sa largeur dynamique, qui est ensuite utilisée comme valeur de keyframe.

Voici un exemple de résultat, basé sur mes préférences de police et de navigateur :

Images clés TranslateX :

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

Images clés de la largeur :

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

Pour résumer la stratégie, l'indicateur d'onglet s'animera désormais sur quatre images clés en fonction de la position d'accrochage au défilement du sélecteur de section. Les points d'accroche créent une délimitation claire entre nos images clés et renforcent la sensation de synchronisation de l'animation.

L&#39;onglet actif et l&#39;onglet inactif sont affichés avec des calques VisBug qui indiquent des scores de contraste conformes pour les deux.

L'utilisateur contrôle l'animation avec son interaction, en voyant la largeur et la position de l'indicateur changer d'une section à l'autre, en suivant parfaitement le défilement.

Vous ne l'avez peut-être pas remarqué, mais je suis très fier de la transition de couleur lorsque l'élément de navigation mis en surbrillance est sélectionné.

Le gris clair non sélectionné semble encore plus en retrait lorsque l'élément sélectionné présente un contraste plus élevé. Il est courant de faire une transition de couleur pour le texte, par exemple au survol et à la sélection, mais il est encore plus avancé de faire une transition de couleur au défilement, synchronisée avec l'indicateur de soulignement.

Voici comment j'ai procédé :

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

Chaque lien de navigation par onglets a besoin de cette nouvelle animation de couleur, qui suit la même timeline de défilement que l'indicateur de soulignement. J'utilise la même timeline qu'avant : comme son rôle est d'émettre un tick au défilement, nous pouvons utiliser ce tick dans n'importe quel type d'animation. Comme précédemment, je crée quatre images clés dans la boucle et renvoie les couleurs.

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

La couleur var(--text-active-color) du keyframe met en évidence le lien. Il s'agit sinon d'une couleur de texte standard. La boucle imbriquée y facilite relativement la tâche, car la boucle externe correspond à chaque élément de navigation et la boucle interne à chaque image clé personnelle de l'élément de navigation. Je vérifie si l'élément de boucle externe est identique à celui de la boucle interne et je l'utilise pour savoir quand il est sélectionné.

J'ai beaucoup aimé écrire cet article. Vraiment.

Encore plus d'améliorations JavaScript

Il est important de rappeler que le cœur de ce que je vous montre ici fonctionne sans JavaScript. Cela dit, voyons comment l'améliorer lorsque JS est disponible.

Les liens profonds sont plutôt un terme mobile, mais je pense que l'intention du lien profond est respectée ici avec les onglets, car vous pouvez partager une URL directement vers le contenu d'un onglet. Le navigateur effectue une navigation sur la page vers l'ID correspondant dans le hachage de l'URL. J'ai trouvé que ce gestionnaire onload permettait d'obtenir l'effet sur toutes les plates-formes.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

Synchronisation de la fin du défilement

Nos utilisateurs ne cliquent pas toujours et n'utilisent pas toujours un clavier. Parfois, ils font simplement défiler l'écran librement, comme ils devraient pouvoir le faire. Lorsque le sélecteur de section arrête de défiler, l'élément sur lequel il s'arrête doit correspondre à celui de la barre de navigation supérieure.

Voici comment j'attends la fin du défilement : js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

Chaque fois que l'utilisateur fait défiler les sections, effacez le délai d'expiration de la section, le cas échéant, et démarrez-en un nouveau. Lorsque le défilement des sections s'arrête, n'effacez pas le délai d'expiration et déclenchez l'événement 100 ms après l'arrêt. Lorsqu'il se déclenche, appelez la fonction qui cherche à déterminer où l'utilisateur s'est arrêté.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

En supposant que le défilement s'est arrêté, la division de la position de défilement actuelle par la largeur de la zone de défilement devrait donner un nombre entier et non un nombre décimal. J'essaie ensuite de récupérer un élément de navigation à partir de notre cache via cet index calculé, et si je trouve quelque chose, j'envoie la correspondance pour qu'elle soit définie comme active.

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

Pour définir l'onglet actif, commencez par effacer tout onglet actif, puis attribuez l'attribut d'état actif à l'élément de navigation entrant. L'appel à scrollIntoView() a une interaction amusante avec le CSS qui mérite d'être notée.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

Dans le CSS de l'utilitaire d'accrochage de défilement horizontal, nous avons imbriqué une requête média qui applique le défilement smooth si l'utilisateur tolère le mouvement. JavaScript peut librement appeler des éléments de défilement dans la vue, et CSS peut gérer l'UX de manière déclarative. Parfois, ils forment un duo très agréable.

Conclusion

Maintenant que vous savez comment j'ai fait, comment feriez-vous ? Voilà une architecture de composants amusante ! Qui va créer la première version avec des emplacements dans son framework préféré ? 🙂

Diversifions nos approches et découvrons toutes les façons de créer sur le Web. Créez votre version sur Glitch, tweetez-la moi, et je l'ajouterai à la section Remix de la communauté ci-dessous.

Remix de la communauté