مزامنة حالة محاذاة مواضع التمرير

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

التنفيذ

1. ضبط ميزة "محاذاة مواضع التمرير" في CSS

يجب أن تحتوي الحاوية على السمة scroll-snap-type المحدّدة، وأن تحتوي على عناصر ثانوية تتضمّن السمة scroll-snap-align لكي يتتبّع المتصفّح أهداف المحاذاة. في مقالة طويلة تحتوي على جدول محتويات، يمكنك استخدام هذه السمة لتثبيت عناوين الأقسام في أعلى إطار العرض.

main {
    /* Enable scroll snapping on the container */  
  scroll-snap-type: y proximity;
  overflow-y: auto;
}

h2 {
  /* Define how headers align when snapped */
  scroll-snap-align: start;
}

2. الاستماع إلى التغييرات في Snap

استخدِم الحدث scrollsnapchange في حاوية الانتقال للتفاعل عندما ينتهي المستخدم من الانتقال ويستقر المتصفّح على عنصر جديد. في العرض التوضيحي لجدول المحتويات، نستخدم هذا لإبراز الرابط النشط في الشريط الجانبي.

<!-- MANDATORY: Wrap table of contents links inside a proper navigation landmark -->
<nav aria-label="Table of contents">
  <ul>
    <li><a href="#section-1" aria-current="location">Section 1</a></li>
    <li><a href="#section-2">Section 2</a></li>
  </ul>
</nav>
const main = document.getElementById('main');
const links = document.querySelectorAll('nav a');

// The event fires when the scroller settles on a new snap target
main.addEventListener('scrollsnapchange', (event) => {
  // Use snapTargetBlock for vertical or snapTargetInline for horizontal
  const snappedHeader = event.snapTargetBlock;
  
  if (snappedHeader) {
    setSelectedParagraph(snappedHeader.id);
  }
});

تسهيل الاستخدام

بالنسبة إلى جدول المحتويات، تأكَّد من أنّ روابط الشريط الجانبي تستخدم aria-current="true" أو aria-current="location" عندما تكون نشطة.

بالإضافة إلى ذلك، يجب توخي الحذر عند استخدام القيمة mandatory لسمة scroll-snap-type، لأنّها قد تؤدي إلى تعذُّر الوصول إلى المحتوى بين نقاط المحاذاة عندما يكون أطول من الشاشة.

استراتيجيات احتياطية

إذا لم يكن scrollsnapchange متاحًا، استخدِم IntersectionObserver لرصد العنصر الذي يظهر حاليًا في أعلى شريط التمرير. يُرجى العِلم أنّ هذا السلوك يختلف عن scrollsnapchange، لأنّه سيتم تفعيله أثناء الانتقال إلى الأسفل، وليس فقط عند توقّف الانتقال.

// Feature detect support for scroll snap events
if (!('onscrollsnapchange' in HTMLElement.prototype)) {
  const observer = new IntersectionObserver(
    () => {
      // Each time the set of intersecting headers changes, find the top
      // header that is visible.
      const topEntry = [...headers].reduce((currentTop, header) => {
        // Use the bottom to handle scrolling up, when the top is still offscreen
        const {bottom} = header.getBoundingClientRect();
        // Don't match if the header's bottom is above the scrollport
        if (bottom < 0) return;
        if (!currentTop) return header;
        return bottom <
          currentTop.getBoundingClientRect().bottom
          ? header
          : currentTop;
      }, undefined);
      if (topEntry) setSelectedParagraph(topEntry.id);
    },
    { root: main, threshold: 0.9 // Adjust based on your use case },
  );

  // Observe all snap targets (e.g., section headers)
  document.querySelectorAll('h2').forEach(header => observer.observe(header));
}