สร้างการนำทาง Scrollspy

ฟีเจอร์บนเว็บ: Scroll marker target pseudo-classes scroll-target-group

การนําทางแบบ Scrollspy เป็นรูปแบบ UI ทั่วไปที่ไฮไลต์ลิงก์การนําทางที่สอดคล้องกับส่วนของหน้าเว็บที่อยู่ในวิวพอร์ตโดยอัตโนมัติ ซึ่งจะช่วยให้ผู้ใช้ทราบได้อย่างชัดเจนว่าตนเองกำลังอ่านส่วนใดของเอกสารที่มีความยาวขณะเลื่อนดู

รูปแบบนี้มักพบใน 2 สถานการณ์ต่อไปนี้

  • เว็บไซต์ที่มีหน้าเดียว: เว็บไซต์ที่ลิงก์การนำทางหลักไปยังส่วนต่างๆ ของหน้าเดียวแทนที่จะเป็น URL แยกกัน
  • เนื้อหาแบบยาว: สารบัญหรือปุ่มนำทางที่แถบด้านข้างจะติดตามความคืบหน้าของผู้ใช้ผ่านบทความ คู่มือ หรือเอกสารประกอบ

วิธีการนำไปใช้งาน

โดยปกติแล้ว รูปแบบ Scrollspy ต้องใช้ JavaScript และ IntersectionObserver API CSS สมัยใหม่มีวิธีแบบเนทีฟในการดำเนินการนี้โดยใช้พร็อพเพอร์ตี้ scroll-target-group และคลาสเทียม :target-current

  1. ตรวจสอบว่าลิงก์การนำทางใช้ตัวระบุ Fragment: การนำทางต้องประกอบด้วยลิงก์ตำแหน่งเฉพาะ (<a>) ที่ชี้ไปยังส่วนต่างๆ ในหน้าเดียวกันโดยใช้ตัวระบุ Fragment (เช่น <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 จะระบุเป้าหมายที่มองเห็นได้ชัดเจนที่สุดเมื่อแต่ละส่วนมีขนาดใหญ่พอที่จะมองเห็นได้ครั้งละ 1 หรือ 2 ส่วนเท่านั้น
  • บังคับ: สถานะภาพต้องสอดคล้องกับ 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>