إنشاء شريط تنقّل Scrollspy

التنقّل باستخدام Scrollspy هو نمط شائع لواجهة المستخدم يميّز تلقائيًا رابط التنقّل المقابل لقسم الصفحة المعروض حاليًا في إطار العرض. يمنح هذا المستخدمين فكرة واضحة عن مكانهم في مستند طويل أثناء التمرير.

هذا النمط شائع في سيناريوهَين:

  • المواقع الإلكترونية ذات الصفحة الواحدة: حيث يؤدي التنقّل الأساسي في الموقع الإلكتروني إلى أقسام مختلفة من صفحة واحدة بدلاً من عناوين URL منفصلة.
  • المحتوى الطويل: حيث يتتبّع جدول المحتويات أو عناصر التنقل في الشريط الجانبي تقدّم المستخدم في قراءة مقالة أو دليل أو مستند.

كيفية الإعداد

في السابق، كان نمط scrollspy يتطلّب استخدام JavaScript وواجهة برمجة التطبيقات IntersectionObserver. توفّر 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.

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