Как создать навигацию Scrollspy

Навигация Scrollspy – это распространенный шаблон пользовательского интерфейса, который автоматически выделяет ссылку навигации, соответствующую разделу страницы, находящемуся в данный момент в области просмотра. Это позволяет пользователям лучше ориентироваться в длинных документах.

Такая модель часто используется в двух случаях:

  • Одностраничные сайты. На таких сайтах ссылки в основном меню ведут на разные разделы одной страницы, а не на отдельные URL.
  • Объемный контент. Оглавление или боковая панель навигации, которые позволяют пользователю отслеживать, какую часть статьи, руководства или документации он уже прочитал.

Рекомендации по внедрению

Раньше для создания scrollspy требовался JavaScript и IntersectionObserver API. Современный CSS предоставляет нативный способ добиться этого с помощью свойства scroll-target-group и псевдокласса :target-current .

  1. Убедитесь, что в ссылках навигации используются идентификаторы фрагментов. Навигация должна состоять из анкорных ссылок (<a>), которые указывают на разделы на той же странице с помощью идентификаторов фрагментов (например, <a href="#section-1">), соответствующих id разделов контента.
  2. Включите группировку целей прокрутки. Примените свойство scroll-target-group: auto к контейнеру ссылок навигации. Это указывает браузеру, что ссылки нужно обрабатывать как маркеры, отслеживающие соответствующие цели.
  3. Оформление активной ссылки. Используйте псевдокласс :target-current для навигационных ссылок, чтобы применять стили, когда целевой раздел виден в контейнере прокрутки.
  4. Семантическое состояние. Используйте скрипт, чтобы обновлять aria-current="true" для активной ссылки, когда прокрутка останавливается. Это позволит правильно передавать состояние технологиям специальных возможностей.

Пример кода

В этом примере активная ссылка в фиксированном меню навигации выделяется, когда пользователь прокручивает разделы.


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

Рекомендации

  • РЕКОМЕНДУЕТСЯ использовать scroll-target-group: auto в специальном контейнере для ссылок навигации. Это позволит браузеру определить, какие ссылки нужно отслеживать.
  • РЕКОМЕНДУЕТСЯ убедиться, что у целей есть уникальные атрибуты id, соответствующие атрибутам href ссылок.
  • РЕКОМЕНДУЕТСЯ оставлять достаточно места по вертикали для разделов. scroll-target-group наиболее четко определяет видимую цель, когда разделы достаточно велики, чтобы одновременно были видны только один или два.
  • ОБЯЗАТЕЛЬНО. Для обеспечения специальных возможностей визуальное состояние должно быть отражено с помощью aria-current. Хотя :target-current обрабатывает визуальный аспект, это не отображается в программах чтения с экрана.
  • AVOID использовать только цвет для обозначения активного состояния. Добавьте другие визуальные подсказки, например насыщенность шрифта, подчеркивание или точку-индикатор, или обеспечьте достаточный контраст между цветами, чтобы разница была заметна пользователям с нарушениями цветового зрения.

Резервные стратегии

Для браузеров, которые не поддерживают scroll-target-group, следует использовать резервный вариант на основе IntersectionObserver API.

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