Créer une navigation Scrollspy

La navigation Scrollspy est un modèle d'UI courant qui met automatiquement en surbrillance le lien de navigation correspondant à la section de la page actuellement affichée dans la fenêtre d'affichage. Les utilisateurs peuvent ainsi savoir clairement où ils se trouvent dans un long document lorsqu'ils le font défiler.

Ce schéma est fréquent dans deux scénarios :

  • Sites Web à page unique : sites où la navigation principale renvoie vers différentes sections d'une même page plutôt que vers des URL distinctes.
  • Contenu long : lorsqu'une table des matières ou une navigation via la barre latérale suit la progression de l'utilisateur dans un article, un guide ou une documentation.

Mise en œuvre

Traditionnellement, le modèle scrollspy nécessitait JavaScript et l'API IntersectionObserver. Le CSS moderne offre un moyen natif d'y parvenir en utilisant la propriété scroll-target-group et la pseudo-classe :target-current.

  1. Assurez-vous que les liens de navigation utilisent des identifiants de fragment : votre navigation doit être composée de liens d'ancrage (<a>) qui pointent vers des sections de la même page à l'aide d'identifiants de fragment (par exemple, <a href="#section-1">) qui correspondent à l'id de vos sections de contenu.
  2. Activer le regroupement des cibles de défilement : appliquez la propriété scroll-target-group: auto au conteneur de vos liens de navigation. Cela indique au navigateur de traiter ces liens comme des marqueurs qui suivent leurs cibles respectives.
  3. Appliquer un style au lien actif : utilisez la pseudo-classe :target-current sur vos liens de navigation pour appliquer des styles lorsque la section cible est visible dans le conteneur de défilement.
  4. État sémantique : utilisez un script pour mettre à jour aria-current="true" sur le lien actif lorsque le défilement s'arrête, afin de vous assurer que l'état est correctement communiqué aux technologies d'assistance.

Exemple de code

Cet exemple met en évidence le lien actif dans un menu de navigation fixe lorsque l'utilisateur fait défiler les sections.


/* MANDATORY: The container of markers must have scroll-target-group: auto */
nav {
  /* Enables automatic tracking of fragment links */
  scroll-target-group: auto;
}
/* MANDATORY: Use :target-current to style the link whose target is in view. */
nav a:target-current {
  color: var(--accent, #007bff);
  border-bottom-color: var(--accent, #007bff);
}
<nav>
  <a href="#intro">Introduction</a>
  <a href="#features">Features</a>
  <a href="#pricing">Pricing</a>
</nav>

<main>
  <!-- MANDATORY: Each section must have a unique ID matching a link fragment in the <nav>. -->
  <section id="intro">
    <h2>Introduction</h2>
    <!-- content -->
  </section>
  <section id="features">
    <h2>Features</h2>
    <!-- content -->
  </section>
  <section id="pricing">
    <h2>Pricing</h2>
    <!-- content -->
  </section>
</main>

<script type="module">
  if (CSS.supports('scroll-target-group: auto')) {
    const syncAriaCurrent = () => {
      const currentLink = document.querySelector('nav a:target-current');
      document.querySelectorAll('nav a').forEach(link => {
        link.setAttribute('aria-current', link === currentLink ? 'true' : 'false');
      });
    };
    // Update the aria on initial page load. Ensure the DOM content is loaded, either by using `<script type="module">` or an event listener for `DOMContentLoaded`.
    syncAriaCurrent();
    // Update the aria after scroll ends
    document.addEventListener('scrollend', syncAriaCurrent);
  }
</script>

Bonnes pratiques

  • À FAIRE : Utilisez scroll-target-group: auto sur un conteneur dédié pour les liens de navigation. Cela permet au navigateur de savoir quels liens doivent être suivis.
  • À FAIRE : Assurez-vous que les cibles ont des attributs id uniques correspondant aux attributs href des liens.
  • À FAIRE : Prévoyez suffisamment d'espace vertical pour les sections. scroll-target-group identifie plus clairement la cible visible lorsque les sections sont suffisamment grandes pour qu'une ou deux seulement soient visibles à la fois.
  • OBLIGATOIRE : pour l'accessibilité, l'état visuel doit être reflété avec aria-current. Bien que :target-current gère l'aspect visuel, cela n'est pas exposé aux lecteurs d'écran.
  • AVOID : Ne vous fiez pas uniquement à la couleur pour l'état actif. Incluez d'autres repères visuels tels que l'épaisseur de la police, un soulignement ou un point indicateur, ou assurez-vous que le contraste entre les couleurs est suffisant pour que la différence soit accessible aux utilisateurs souffrant de déficiences visuelles.

Stratégies de remplacement

Pour les navigateurs qui ne sont pas compatibles avec scroll-target-group, vous devez utiliser une solution de remplacement basée sur l'API IntersectionObserver.

<script>
  // Feature detection for scroll-target-group
  const supportsScrollTargetGroup = CSS.supports('scroll-target-group: auto');

  if (!supportsScrollTargetGroup) {
    const navLinks = document.querySelectorAll('nav a');
    const sections = document.querySelectorAll('main section');

    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const id = entry.target.getAttribute('id');
          navLinks.forEach(link => {
            const isActive = link.getAttribute('href') === `#${id}`;
            // Use the escaped colon class for consistent styling
            link.classList.toggle('\:target-current', isActive);
            link.setAttribute('aria-current', isActive);
          });
        }
      });
    }, { rootMargin: '-50% 0px -50% 0px', threshold: 0 });

    sections.forEach(section => observer.observe(section));
  }
</script>

<style>
  /* Fallback style if :target-current is not supported.
     Use :is() to combine the native pseudo-class with a fallback class for older browsers. DO NOT use `nav a:target-current, nav a.\:target-current, as the unrecognized pseudoselector invalidates the entire rule in older browsers. */
  nav a:is(:target-current, .\:target-current) {
    color: var(--accent, #007bff);
    border-bottom-color: var(--accent, #007bff);
    font-weight: bold;
  }
</style>