Xây dựng thanh điều hướng Scrollspy

Thanh điều hướng theo dõi cuộn là một mẫu giao diện người dùng phổ biến, tự động làm nổi bật đường liên kết điều hướng tương ứng với phần của trang hiện đang nằm trong khung nhìn. Điều này giúp người dùng biết rõ vị trí của họ trong một tài liệu dài khi họ di chuyển.

Mẫu này thường gặp trong hai trường hợp:

  • Trang web một trang: Trang web có trình đơn điều hướng chính liên kết đến các phần khác nhau của một trang duy nhất thay vì các URL riêng biệt.
  • Nội dung dài: Trong đó mục lục hoặc điều hướng bằng thanh bên theo dõi tiến trình của người dùng thông qua một bài viết, hướng dẫn hoặc tài liệu.

Cách triển khai

Thông thường, mẫu scrollspy yêu cầu JavaScript và API IntersectionObserver. CSS hiện đại cung cấp một cách gốc để đạt được điều này bằng cách sử dụng thuộc tính scroll-target-group và lớp giả :target-current.

  1. Đảm bảo đường liên kết điều hướng sử dụng giá trị nhận dạng mảnh: Hoạt động điều hướng phải bao gồm các đường liên kết neo (<a>) trỏ đến các phần trên cùng một trang bằng cách sử dụng giá trị nhận dạng mảnh (ví dụ: <a href="#section-1">) khớp với id của các phần nội dung.
  2. Bật tính năng Nhóm mục tiêu cuộn: Áp dụng thuộc tính scroll-target-group: auto cho vùng chứa của các đường liên kết điều hướng. Điều này cho trình duyệt biết rằng các đường liên kết này là điểm đánh dấu theo dõi mục tiêu tương ứng.
  3. Tạo kiểu cho đường liên kết đang hoạt động: Sử dụng lớp giả :target-current trên các đường liên kết điều hướng để áp dụng kiểu khi phần mục tiêu của các đường liên kết đó xuất hiện trong vùng chứa cuộn.
  4. Trạng thái ngữ nghĩa: Sử dụng tập lệnh để cập nhật aria-current="true" trên đường liên kết đang hoạt động khi thao tác cuộn dừng lại để đảm bảo trạng thái được truyền đạt chính xác đến các công nghệ hỗ trợ.

Mã mẫu

Ví dụ này làm nổi bật đường liên kết đang hoạt động trong trình đơn điều hướng cố định khi người dùng di chuyển qua các mục.


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

Các phương pháp hay nhất

  • NÊN sử dụng scroll-target-group: auto trên một vùng chứa riêng cho các đường liên kết điều hướng: Việc này giúp trình duyệt biết được những đường liên kết nào cần theo dõi.
  • NÊN đảm bảo các mục tiêu có thuộc tính id riêng biệt khớp với thuộc tính href của đường liên kết.
  • NÊN cung cấp đủ không gian dọc cho các phần: scroll-target-group xác định rõ nhất mục tiêu hiển thị khi các phần đủ lớn để chỉ hiển thị một hoặc hai phần cùng một lúc.
  • BẮT BUỘC: Để hỗ trợ tiếp cận, trạng thái hình ảnh phải được phản ánh bằng aria-current. Mặc dù :target-current xử lý khía cạnh hình ảnh, nhưng trình đọc màn hình không hiển thị thông tin này.
  • AVOID chỉ dựa vào màu sắc cho trạng thái đang hoạt động: Thêm các dấu hiệu hình ảnh khác như độ đậm phông chữ, dấu gạch dưới hoặc dấu chấm chỉ báo, hoặc đảm bảo có đủ độ tương phản giữa các màu để người dùng bị mù màu có thể nhận ra sự khác biệt.

Chiến lược dự phòng

Đối với những trình duyệt không hỗ trợ scroll-target-group, bạn nên sử dụng phương án dự phòng dựa trên 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>