Crea una navigazione Scrollspy

La navigazione con scrollspy è un pattern di UI comune che evidenzia automaticamente il link di navigazione corrispondente alla sezione della pagina attualmente visibile nell'area visibile. In questo modo, gli utenti hanno un'idea chiara di dove si trovano in un documento lungo mentre scorrono.

Questo pattern è comune in due scenari:

  • Siti web a singola pagina: in cui la navigazione principale del sito rimanda a diverse sezioni di una singola pagina anziché a URL separati.
  • Contenuti in formato lungo: dove un sommario o una barra di navigazione laterale monitora l'avanzamento dell'utente in un articolo, una guida o una documentazione.

Come implementare

In genere, il pattern scrollspy richiedeva JavaScript e l'API IntersectionObserver. I CSS moderni offrono un modo nativo per ottenere questo risultato utilizzando la proprietà scroll-target-group e la pseudo-classe :target-current.

  1. Assicurati che i link di navigazione utilizzino identificatori di frammenti: la navigazione deve essere costituita da link di ancoraggio (<a>) che rimandano a sezioni della stessa pagina utilizzando identificatori di frammenti (ad es. <a href="#section-1">) che corrispondono all'id delle sezioni dei contenuti.
  2. Attiva il raggruppamento dei target di scorrimento: applica la proprietà scroll-target-group: auto al contenitore dei link di navigazione. In questo modo, il browser tratta questi link come indicatori che monitorano i rispettivi target.
  3. Stile del link attivo: utilizza la pseudo-classe :target-current sui link di navigazione per applicare gli stili quando la sezione di destinazione è visibile nel contenitore di scorrimento.
  4. Stato semantico: utilizza uno script per aggiornare aria-current="true" sul link attivo quando lo scorrimento si interrompe per assicurarti che lo stato venga comunicato correttamente alle tecnologie per la disabilità.

Esempio di codice

Questo esempio evidenzia il link attivo in un menu di navigazione fisso mentre l'utente scorre le sezioni.


/* 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>

Best practice

  • SÌ: utilizza scroll-target-group: auto su un contenitore dedicato per i link di navigazione. In questo modo, il browser sa quali link devono essere monitorati.
  • SÌ: assicurati che i target abbiano attributi id univoci corrispondenti agli attributi href dei link.
  • SÌ: fornisci spazio verticale sufficiente per le sezioni. scroll-target-group identifica più chiaramente il target visibile quando le sezioni sono abbastanza grandi da consentire la visualizzazione di una o due sezioni alla volta.
  • OBBLIGATORIO: per l'accessibilità, lo stato visivo deve essere rispecchiato con aria-current. Sebbene :target-current gestisca l'aspetto visivo, questo non viene esposto agli screen reader.
  • AVOID di fare affidamento solo sul colore per lo stato attivo: includi altri indicatori visivi come lo spessore del carattere, una sottolineatura o un punto indicatore oppure assicurati che ci sia un contrasto sufficiente tra i colori per garantire che la differenza sia accessibile agli utenti con daltonismo.

Strategie di riserva

Per i browser che non supportano scroll-target-group, devi utilizzare un fallback basato sull'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>