Créer un composant "Tabs"

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

Dans cet article, je vais vous expliquer comment créer un composant Tabs pour le Web qui soit responsif, compatible avec plusieurs entrées d'appareil et compatible avec plusieurs navigateurs. Essayez la démonstration.

Démonstration

Si vous préférez la vidéo, 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. Tout d'abord, nous avons commencé par créer des onglets pour ordinateur basés sur l'élément <frame>. Désormais, nous disposons de composants mobiles à beurre qui animent le contenu en fonction de propriétés physiques. Ils essaient tous de faire la même chose: gagner de l'espace.

Aujourd'hui, l'essentiel de l'expérience utilisateur dans les onglets est une zone de navigation à boutons qui active/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 montage est assez chaotique en raison de la grande diversité des styles appliqués par le Web au concept de composant.
Un montage de styles de conception Web de composants d'onglet datant des 10 dernières années

Tactique 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 de balayage et de clavier avec les positions d'arrêt de défilement appropriées
  • Les liens profonds via des hachages d'URL pour la prise en charge du partage et de l'ancrage du défilement sur la page par le navigateur
  • Compatibilité des lecteurs d'écran avec le 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 à l'état de brouillon permettant de souligner et de modifier la couleur de l'onglet sélectionné de manière dynamique

Le code HTML

Fondamentalement, l'expérience utilisateur ici consiste à 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 membres de contenu structurel: les liens et les :target. Nous avons besoin d'une liste de liens, pour laquelle un <nav> est idéal, et d'une liste d'éléments <article>, ce qui est idéal pour un <section>. Chaque hachage de lien correspond à une section, ce qui permet au navigateur de faire défiler les éléments via l'ancrage.

L'utilisateur clique sur un bouton de lien qui glisse dans le contenu sélectionné

Par exemple, lorsque vous cliquez sur un lien, l'article :target est automatiquement sélectionné dans Chrome 89, aucun code JS n'est requis. 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 mélangées de lorem, et les liens avec un ensemble de titres avec une longueur et un ensemble d'images. Nous pouvons alors commencer la mise en page.

Mises en page avec défilement

Il existe trois types de zones de défilement différents dans ce composant:

  • La navigation (rose) peut être défilée horizontalement.
  • Vous pouvez faire défiler la zone de contenu (bleue) horizontalement.
  • Le défilement vertical peut être effectué sur chaque élément d'article (vert).
Trois boîtes colorées avec des flèches directionnelles de couleurs assorties qui délimitent les zones de défilement et indiquent la direction dans laquelle elles se déplacent.

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

  1. Une fenêtre
    Zone avec des dimensions définies et 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 d'article.

Mise en page <snap-tabs>

La mise en page de premier niveau que j'ai choisie était 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. Elle masque tous les éléments dont le dépassement est masqué. L'en-tête et la section utiliseront bientôt un 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 défilements:

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

Les frames VisBug que j'ai mis en surbrillance ci-dessous 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 comportent des superpositions roses, décrivant l&#39;espace qu&#39;ils occupent dans le composant.

Mise en page des onglets <header>

La mise en page suivante est presque la même: j'utilise un 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 aide à définir cette étape. Aucun élément en position absolue ici.

les éléments nav et span.indicator comportent des superpositions roses, décrivant 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 horizontales (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'entre eux nécessite un débordement sur l'axe X, un confinement du défilement pour piéger le défilement hors limites, des barres de défilement cachées pour les appareils tactiles et enfin un composant Scroll-Snap pour le verrouillage des zones de présentation de contenu. L'ordre de tabulation du clavier est accessible et toutes les interactions guident la mise au point naturellement. Les conteneurs d'ancrage de défilement obtiennent également une interaction de style carrousel depuis leur clavier.

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

Les liens de navigation doivent être alignés sur une ligne, sans saut de ligne, centrés verticalement et chaque élément de lien doit s'ancrer au conteneur d'ancrage de défilement. Bravo, Swift 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;
  }
}

Le style et la taille de chaque lien sont eux-mêmes donnés. La mise en page de navigation ne doit donc spécifier que la direction et le flux. Les largeurs uniques des éléments de navigation facilitent la transition entre les onglets, car l'indicateur adapte sa largeur à la nouvelle cible. Selon le nombre d'éléments ici, le navigateur affiche ou non une barre de défilement.

Les éléments &quot;a&quot; de la barre de navigation comportent des superpositions roses, qui indiquent l&#39;espace qu&#39;ils occupent dans le composant ainsi que l&#39;endroit où ils débordent.

Mise en page des onglets <section>

Cette section est un élément flexible qui doit être le principal consommateur d'espace. Il doit également créer des colonnes dans lesquelles placer les articles. Encore une fois, faites un travail rapide pour CSS 2021 ! block-size: 100% étire cet élément pour remplir le parent autant que possible, puis crée une série de colonnes dont la largeur est 100% pour sa propre mise en page. Les pourcentages fonctionnent très bien ici car nous avons écrit 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 voulions "développer verticalement autant que possible, de manière agressive" (pensez à l'en-tête que nous avons défini sur flex-shrink: 0: il constitue une défense contre cette poussée d'expansion), qui définit la hauteur de la ligne pour un ensemble de colonnes pleine hauteur. Le style auto-flow indique à la grille de toujours disposer les enfants sur une ligne horizontale sans encapsulation, exactement ce que nous voulons, pour qu'ils dépassent la fenêtre parent.

Les éléments de l&#39;article comportent des superpositions roses, qui indiquent l&#39;espace qu&#39;ils occupent dans le composant et où ils débordent.

J'ai parfois du mal à me couvrir ce sujet ! Cet élément de section s'adapte à une boîte, mais a également créé un ensemble de boîtes. J’espère que les éléments visuels et les explications vous aideront.

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 apparaître qu'en cas de dépassement. Ces éléments de l'article sont bien positionnés. Ils sont à la fois un parent de défilement et un enfant de défilement. Ici, le navigateur gère vraiment des interactions délicates au toucher, à la souris et au clavier.

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 conteneur de défilement parent. J'aime beaucoup la façon dont les éléments des liens de navigation et les éléments d'article s'alignent sur le début intégré de leurs conteneurs de défilement respectifs. Cela ressemble à une relation harmonieuse.

L&#39;élément &quot;article&quot; et ses éléments enfants comportent des superpositions roses, décrivant l&#39;espace qu&#39;ils occupent dans le composant et la direction dans laquelle ils débordent.

L'article est un enfant de grille, et sa taille est prédéterminée comme la zone de la fenêtre d'affichage que nous souhaitons fournir à l'expérience de défilement. Cela signifie que je n'ai pas besoin d'un style de hauteur ou de largeur ici, je dois juste définir la façon dont il déborde. Je définis "overflow-y" sur "auto", puis je bloque les interactions de défilement à l'aide de la propriété pratique "outscroll-behavior".

Récapitulatif des 3 zones de défilement

Ci-dessous, j'ai choisi dans mes 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 lorsque ce paramètre est activé, car il s'agit d'examiner la mise en page et l'orchestration du défilement.

les trois barres de défilement sont configurées pour s&#39;afficher, ce qui occupe désormais de l&#39;espace de mise en page et le composant semble toujours parfait.

Je pense que voir la marge de la barre de défilement dans ce composant permet de montrer clairement où se trouvent les zones de défilement, dans quelle direction elles prennent en charge et comment 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 flexible ou de grille dans une mise en page.

Les outils de développement peuvent nous aider à nous faire une idée:

les zones de défilement comportent des superpositions d&#39;outil Grille et Flexbox, qui indiquent l&#39;espace qu&#39;elles occupent dans le composant et la direction dans laquelle elles débordent.
Les outils de développement Chromium, qui montrent la mise en page de l'élément de navigation Flexbox avec des éléments d'ancrage, la mise en page des sections sous forme de grille avec tous les éléments de l'article, et les éléments de l'article avec des paragraphes et un élément de titre.

Les mises en page du défilement sont complètes: ancrage, lien profond et accès au clavier. Base solide pour l'amélioration de l'expérience utilisateur, le style et le plaisir.

Présentation des fonctionnalités

Les enfants accrochés au défilement conservent leur position verrouillée lors du redimensionnement. Cela signifie que JavaScript n'a 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 de l'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 sa mise en œuvre conformément aux spécifications. Voici un article de blog à ce sujet.

Animation

L'objectif du travail d'animation est de lier clairement les interactions avec les commentaires de l'UI. Cela permet de guider ou d'aider l'utilisateur à découvrir facilement tout le contenu. J'ajouterai du mouvement avec un but 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.

J'associe le soulignement d'un onglet à la position de défilement de l'article. L'ancrage n'est pas seulement un joli alignement, il ancre également le début et la fin d'une animation. Ainsi, <nav>, qui agit comme une mini-carte, reste connecté au contenu. Nous vérifierons les préférences de mouvement de l'utilisateur à partir de CSS et de JavaScript. Il y a quelques endroits parfaits pour 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, c'est instantané. Le navigateur définit simplement la position de défilement. Et si nous voulons passer à cette position de défilement au lieu de cligner des yeux ?

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

Étant donné que nous introduisons ici le mouvement et le 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 les mouvements réduits. De cette façon, nous n'introduisons le mouvement de défilement que pour les utilisateurs qui s'y prêtent.

Indicateur d'onglets

Le but de cette animation est d'associer l'indicateur à l'état du contenu. J'ai décidé de colorer les styles border-bottom en fondu enchaîné pour les utilisateurs qui préfèrent les mouvements réduits, et d'une animation de défilement et de fondu de couleur pour les utilisateurs qui acceptent le mouvement.

Dans les outils de développement Chromium, je peux activer/désactiver la préférence et afficher les deux styles de transition différents. J'ai pris beaucoup de plaisir à construire ça.

@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 le .snap-indicator lorsque l'utilisateur préfère un mouvement réduit, car je n'en ai plus besoin. Ensuite, je le remplace par des styles border-block-end et un transition. Notez également, dans l'interaction avec les onglets, que non seulement l'élément de navigation actif est souligné sur la marque, mais que la couleur du texte est également plus sombre. L'élément actif présente un contraste de couleurs de texte plus élevé et un accent de lumière vive.

Quelques lignes de code CSS supplémentaires permettent de se sentir remarqué (dans le sens où nous respectons soigneusement 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é à mouvement réduit. Dans cette section, je vais vous montrer comment j'ai associé l'indicateur à une zone de défilement. Voici des fonctionnalités expérimentales amusantes à venir. 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 les préférences de mouvement de l'utilisateur à partir de JavaScript. Si le résultat est false, ce qui signifie que l'utilisateur préfère un mouvement réduit, nous n'exécuterons aucun des effets de mouvement de liaison de défilement.

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

Au moment de la rédaction de ce document, @scroll-timeline n'est pas compatible avec les navigateurs. Il s'agit d'un brouillon de spécification qui n'est disponible qu'à titre expérimental. Il comporte un polyfill, que j'utilise dans cette démonstration.

ScrollTimeline

Bien que CSS et JavaScript permettent tous deux de créer des timelines de défilement, j'ai activé JavaScript afin de pouvoir utiliser les mesures des é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, scrollSource. Normalement, une animation sur le Web s'exécute sur une période globale, mais avec un sectionScrollTimeline personnalisé en mémoire, je peux modifier tout cela.

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

Avant d'aborder les images clés de l'animation, je pense qu'il est important de souligner le followers du défilement, tabindicator, qui sera animé en fonction d'une timeline personnalisée, le défilement de notre section. L'association est terminée, mais il manque l'ingrédient final (les points avec état) entre lesquels s'animer (également appelés images clés).

Images clés dynamiques

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

Toutefois, JavaScript sait comment obtenir ces informations. Nous allons donc itérer sur les éléments 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. Quatre images clés de transformation sont créées pour l'animation. Il en va de même pour la largeur. On demande à chaque personne quelle est sa largeur dynamique, puis elle est 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 en 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'anime désormais sur quatre images clés en fonction de la position d'ancrage du défilement du conteneur de défilement de la section. Les points d'ancrage créent une séparation claire entre les images clés et renforcent réellement l'aspect synchronisé de l'animation.

les onglets actifs et inactifs sont affichés avec des superpositions VisBug qui indiquent les scores de contraste obtenus pour ces deux onglets.

L'utilisateur pilote l'animation lors de son interaction. La largeur et la position de l'indicateur changent d'une section à l'autre, avec un suivi optimal du 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 en surbrillance est sélectionné.

Le gris clair non sélectionné est encore plus repoussé lorsque l'élément en surbrillance est plus contrasté. Il est courant de changer la couleur de transition du texte, par exemple lorsque l'utilisateur pointe sur l'élément et lorsqu'il est sélectionné, mais l'étape suivante consiste à modifier cette couleur lors du défilement, synchronisé avec l'indicateur de soulignement.

Voici comment j'ai fait:

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 onglet nécessite cette nouvelle animation en couleur, qui suit la même chronologie de défilement que l'indicateur de soulignement. J'utilise la même timeline que précédemment: comme son rôle est d'émettre un tick lors du défilement, nous pouvons l'utiliser dans n'importe quel type d'animation. Comme je l'ai fait 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. Autrement dit, il s'agit d'une couleur de texte standard. La boucle imbriquée la rend relativement simple, car la boucle externe est chaque élément de navigation, et la boucle interne est les 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 ça. Vraiment.

Encore plus d'améliorations JavaScript

N'oubliez pas que l'essentiel de ce que je vous montre ici fonctionne sans JavaScript. Voyons maintenant comment l'améliorer lorsque JavaScript est disponible.

Les liens profonds sont plus un terme mobile, mais je pense que leur objectif est ici avec les onglets, dans la mesure où vous pouvez partager une URL directement vers le contenu d'un onglet. Le navigateur accède alors à l'ID correspondant dans le hachage de l'URL. J'ai constaté que ce gestionnaire onload avait généré l'effet sur toutes les plates-formes.

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

Fin de la synchronisation du défilement

Nos utilisateurs n'ont pas toujours le temps de cliquer ou d'utiliser un clavier. Parfois, ils le font simplement, comme ils devraient pouvoir le faire. Lorsque le conteneur de défilement arrête de faire défiler la section, l'endroit où il atterrit 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 défilement des sections, effacez le délai avant expiration de la section le cas échéant et démarrez-en une nouvelle. Lorsqu'une section cesse de faire défiler une section, n'effacez pas le délai avant expiration et déclenchez-la 100 ms après l'arrêt. Lorsqu'il se déclenche, appelez une 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 soit ancré, la division de la position de défilement actuelle par rapport à la largeur de la zone de défilement devrait donner un nombre entier et non une décimale. J'essaie ensuite de récupérer un élément navitem de notre cache via cet index calculé et, s'il trouve un élément, j'envoie la correspondance pour qu'elle soit 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() présente une interaction amusante avec le 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 d'ancrage de défilement horizontal, nous avons imbriqué une requête média qui applique le défilement smooth si l'utilisateur est tolérant aux mouvements. JavaScript peut effectuer librement des appels pour faire défiler les éléments afin qu'ils s'affichent, et CSS peut gérer l'expérience utilisateur de manière déclarative. Quelle belle petite rencontre qu'ils font parfois.

Conclusion

Maintenant que tu sais comment j'ai fait, comment tu en ferais ?! Cela donne une architecture de composants amusante ! Qui créera la 1re version avec des emplacements dans son framework préféré ? 🙂

Diversissons nos approches et apprenons toutes les façons de créer sur le Web. Créez un Glitch, envoyez-moi votre version par tweet et je l'ajouterai à la section Remix de la communauté ci-dessous.

Remix de la communauté