Créer un composant "Tabs"

Présentation de base de la création d'un composant d'onglets semblable à ceux des applications iOS et Android.

Dans cet article, je souhaite partager mes réflexions sur la création d'un composant d'onglets pour le Web qui soit responsive, compatible avec plusieurs entrées d'appareil et compatible avec tous les navigateurs. Essayez la démo.

Démo

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

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 basés sur l'élément <frame>, et nous disposons désormais de 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 boutons qui permet d'activer ou de désactiver 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é des styles que le Web a appliqués au concept de composant
Collage de styles de conception Web de composants d'onglets 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 l'ancrage du défilement dans la page géré par le navigateur et la prise en charge du partage
  • Compatibilité avec les lecteurs d'écran grâce au balisage des éléments <a> et id="#hash"
  • prefers-reduced-motion pour activer les transitions en fondu enchaîné et le défilement instantané sur la page
  • La fonctionnalité Web @scroll-timeline en cours de rédaction pour souligner et changer de couleur de manière dynamique l'onglet sélectionné

Le code HTML

Fondamentalement, l'expérience utilisateur 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 à mesure que le navigateur fait défiler la page jusqu'à l'élément correspondant.

Il contient des éléments de contenu structurels : 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 correspondra à une section, ce qui permettra au navigateur de faire défiler les éléments via l'ancrage.

Un clic sur un bouton de lien fait glisser le contenu ciblé

Par exemple, cliquer sur un lien permet de cibler automatiquement l'article :target dans Chrome 89, sans qu'aucun code JS ne soit nécessaire. 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 complémentaire, 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 texte lorem, et les liens avec des titres de longueur variable et des images. 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 (rose) peut être défilée horizontalement
  • La zone de contenu (en bleu) peut être défilée horizontalement.
  • Chaque article (en vert) peut être défilé verticalement.
Trois cadres colorés avec des flèches directionnelles de la même couleur qui délimitent les zones de défilement et indiquent la direction du défilement.

Le défilement implique deux types d'éléments différents :

  1. Une fenêtre
    Une boîte aux dimensions définies qui possède le style de propriété overflow.
  2. Une 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 niveau supérieur que j'ai choisie est Flex (Flexbox). Je définis la direction sur column, de sorte que l'en-tête et la section soient ordonnés verticalement. Il s'agit de notre première fenêtre de défilement, et elle masque tout avec overflow : hidden. L'en-tête et la section utiliseront bientôt le défilement au-delà des 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; } }

Pointez de nouveau vers le diagramme coloré à trois parchemins :

  • <header> est maintenant prêt à être le conteneur de défilement (rose).
  • <section> est prêt à être 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'en-tête et de section sont recouverts d'une superposition rose vif, qui délimite l'espace qu'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, et cette mise en page de l'en-tête permet de définir cette étape. Aucun élément positionné de manière absolue ici.

Les éléments nav et span.indicator sont recouverts d'une superposition rose vif, qui délimite l'espace qu'ils occupent dans le composant.

Passons maintenant aux 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 nécessite un dépassement sur l'axe X, un confinement du défilement pour piéger le défilement hors limites, des barres de défilement masquées pour les appareils tactiles et, enfin, un défilement scroll-snap pour verrouiller les zones de présentation du contenu. L'ordre de tabulation du clavier est accessible, et toutes les interactions guident naturellement le curseur. Les conteneurs scroll-snap bénéficient également d'une interaction de style carrousel agréable à partir de 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. Bravo pour votre travail rapide pour le CSS 2021 !

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 se stylise et se dimensionne lui-même. La mise en page de la navigation n'a donc qu'à spécifier 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. Selon le nombre d'éléments qu'il contient, le navigateur affichera ou non une barre de défilement.

Les éléments &quot;a&quot; de l'élément &quot;nav&quot; sont recouverts d'une superposition rose vif, qui délimite l'espace qu'ils occupent dans le composant ainsi que l'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 placer les articles. Encore une fois, un travail rapide pour CSS 2021 ! block-size: 100% étire cet élément pour remplir le parent autant que possible, puis, pour sa propre mise en page, il crée une série de colonnes qui représentent 100% de la largeur du parent. Les pourcentages fonctionnent très bien ici, car nous avons défini des contraintes strictes 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 agressive " (rappelez-vous que l'en-tête que nous avons défini sur flex-shrink: 0 est une défense contre cette expansion agressive), ce qui définit la hauteur de ligne pour un ensemble de colonnes de hauteur maximale. Le style auto-flow indique à la grille de toujours disposer les éléments enfants sur une ligne horizontale, sans retour à la ligne, ce qui correspond exactement à ce que nous voulons : dépasser la fenêtre parente.

Les éléments d'article sont recouverts d'une superposition rose vif, qui délimite l'espace qu'ils occupent dans le composant et l'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 crée également un ensemble de boîtes. J'espère que les 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 que si le contenu dépasse la taille de l'écran. Ces éléments d'article sont bien positionnés. Ils sont à la fois un parent et un enfant de défilement. Le navigateur gère ici des interactions tactiles, à la souris et au clavier assez complexes.

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 la ligne de leurs conteneurs de défilement respectifs. On dirait une relation harmonieuse.

L'élément article et ses éléments enfants sont recouverts d'une superposition rose vif, qui délimite l'espace qu'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 dans laquelle nous voulons fournir une expérience utilisateur 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. Je définis overflow-y sur auto, puis je capture également les interactions de défilement avec la propriété overscroll-behavior.

Récapitulatif des trois zones de défilement

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

Les trois barres de défilement sont configurées pour s'afficher, occupant désormais de l'espace dans la mise en page, et notre composant est toujours aussi beau.

Je pense que la gouttière de la barre de défilement dans ce composant permet de voir clairement où se trouvent les zones de défilement, la direction qu'elles prennent en charge et comment elles interagissent entre elles. Notez que chacun de ces cadres de fenêtre de défilement est également un parent flex ou grid d'une mise en page.

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

les zones de défilement comportent des superpositions d'outils de grille et de flexbox, qui délimitent l'espace qu'elles occupent dans le composant et la direction dans laquelle elles débordent
Outils pour les développeurs Chromium, montrant 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 la 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 de défilement sont complètes : ancrage, liens profonds et accessibilité au clavier. Une base solide pour améliorer l'expérience utilisateur, le style et l'attrait.

Fonctionnalité mise en avant

Les enfants avec défilement scroll-snap 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 Responsive, 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 qu'elle corresponde à la spécification. Voici un article de blog à ce sujet.

Animation

L'objectif de l'animation est de lier clairement les interactions aux commentaires de l'UI. Cela permet de guider ou d'aider l'utilisateur à découvrir l'ensemble du contenu de manière (espérons-le) fluide. Je vais ajouter du mouvement de manière intentionnelle et conditionnelle. Les utilisateurs peuvent désormais spécifier leurs préférences de mouvement dans leur système d'exploitation, et j'apprécie beaucoup de répondre à leurs préférences dans mes interfaces.

Je vais associer le soulignement d'un onglet à la position de défilement de l'article. L'alignement n'est pas seulement un alignement esthétique, il permet également d'ancrer le début et la fin d'une animation. Cela permet de conserver la <nav>, qui agit comme une mini-carte, connectée au contenu. Nous allons vérifier la préférence de mouvement de l'utilisateur à partir de CSS et de JS. Voici quelques endroits où vous devez faire attention.

Comportement de défilement

Il est possible d'améliorer le comportement de mouvement de :target et de element.scrollIntoView(). Par défaut, l'envoi est instantané. 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;
  }
}

Comme 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 pas de préférence dans son système d'exploitation concernant la réduction des mouvements. De cette façon, nous n'introduisons le mouvement de 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é de couleur border-bottom pour les utilisateurs qui préfèrent une animation réduite, et une animation de glissement et de fondu de couleur liée au défilement pour les utilisateurs qui acceptent l'animation.

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

@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 l'élément .snap-indicator lorsque l'utilisateur préfère une animation réduite, car je n'en ai plus besoin. Ensuite, je le remplace par des styles border-block-end et un transition. Notez également que dans l'interaction des onglets, l'élément de navigation actif est non seulement souligné par une couleur de la marque, mais la couleur de son texte est également plus sombre. 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 permettront à un utilisateur de se sentir vu (dans le sens où nous respectons scrupuleusement ses préférences de mouvement). J'adore.

@scroll-timeline

Dans la section ci-dessus, je vous ai montré comment je gère les styles de fondu enchaîné avec réduction des animations. Dans cette section, je vais vous montrer comment j'ai associé l'indicateur et une zone de défilement. Nous allons maintenant passer à 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 commence par vérifier 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 une réduction des mouvements, nous n'exécuterons aucun des effets de mouvement liés au défilement.

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

Au moment de la rédaction de cet article, la compatibilité des navigateurs avec @scroll-timeline est nulle. Il s'agit d'un projet de spécification avec uniquement des implémentations expérimentales. Il existe toutefois un polyfill, que j'utilise dans cette démonstration.

ScrollTimeline

Bien que CSS et JavaScript puissent tous deux créer des chronologies 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 en fonction d'un intervalle de temps 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 d'aborder les images clés de l'animation, il est important de souligner que l'élément qui suit le défilement, tabindicator, sera animé en fonction d'une chronologie personnalisée, à savoir le défilement de notre section. La liaison est ainsi terminée, mais il manque l'ingrédient final : les points avec état entre lesquels l'animation doit s'effectuer, également appelés images clés.

Images clés dynamiques

Il existe un moyen CSS déclaratif pur très puissant d'animer avec @scroll-timeline, mais l'animation que j'ai choisi de faire était trop dynamique. Il n'est pas possible d'effectuer une transition entre les largeurs auto, ni de créer de manière dynamique un certain nombre d'images clés en fonction de la longueur des enfants.

JavaScript sait comment obtenir ces informations. Nous allons donc parcourir nous-mêmes les enfants 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 d'image clé.

Voici un exemple de résultat, basé sur mes polices et mes préférences 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 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 de défilement scroll-snap du défileur de section. Les points d'ancrage créent une délimitation claire entre nos images clés et renforcent l'impression de synchronisation de l'animation.

L'onglet actif et l'onglet inactif sont affichés avec des superpositions VisBug qui indiquent des scores de contraste satisfaisants pour les deux.

L'utilisateur contrôle l'animation par 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 mis en surbrillance présente un contraste plus élevé. Il est courant d'effectuer une transition de couleur pour le texte, par exemple au survol et lors de la sélection, mais il est encore plus intéressant d'effectuer une transition de cette couleur lors du 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 d'onglet a besoin de cette nouvelle animation de couleur, qui suit la même chronologie de défilement que l'indicateur de soulignement. J'utilise la même chronologie qu'auparavant : comme son rôle est d'émettre un tick lors du 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 je renvoie des 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)",
]

L'image clé avec la couleur var(--text-active-color) met en surbrillance le lien, qui est sinon d'une couleur de texte standard. La boucle imbriquée rend l'opération relativement simple, car la boucle externe correspond à chaque élément de navigation et la boucle interne correspond aux images clés personnelles de chaque élément de navigation. Je vérifie si l'élément de la boucle externe est identique à celui de la boucle interne, et je l'utilise pour savoir quand il est sélectionné.

J'ai pris beaucoup de plaisir à écrire ce code. Vraiment.

Encore plus d'améliorations JavaScript

Il est utile de rappeler que l'essentiel 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 accède à l'ID correspondant dans le hachage de l'URL. J'ai découvert que ce gestionnaire onload appliquait 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

Les utilisateurs ne cliquent pas toujours et n'utilisent pas toujours un clavier. Parfois, ils font simplement défiler l'écran, comme ils doivent pouvoir le faire. Lorsque le défilement de la section s'arrête, l'endroit où il s'arrête doit correspondre à 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 avant expiration de la section, le cas échéant, et démarrez-en un nouveau. Lorsque les sections cessent de défiler, n'effacez pas le délai avant 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 scroll-snap est activé, diviser la position de défilement actuelle par la largeur de la zone de défilement devrait donner un entier et non un décimal. J'essaie ensuite de récupérer un élément de navigation dans notre cache via cet index calculé. S'il trouve une correspondance, je l'envoie 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 l'onglet actuellement actif, puis attribuez l'attribut d'état actif à l'élément de navigation entrant. L'appel à scrollIntoView() a une interaction intéressante avec CSS qu'il convient de noter.

.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 de défilement scroll-snap horizontal, nous avons imbriqué une requête média qui applique le défilement smooth si l'utilisateur tolère les mouvements. JavaScript peut effectuer des appels pour faire défiler les éléments dans la vue, et CSS peut gérer l'expérience utilisateur de manière déclarative. Ils forment parfois un duo tout à fait charmant.

Conclusion

Maintenant que vous savez comment j'ai fait, comment feriez-vous ? Cela permet de créer 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 un Glitch, tweetez-moi votre version et je l'ajouterai à la section Remix de la communauté ci-dessous.

Remix par la communauté