סנכרון מצב של גלילה עם נקודות עצירה (scroll snapping)

בעבר, כדי לסנכרן את ערך דינמי של ממשק המשתמש עם מיקום ההצמדה של קונטיינר שניתן לגלילה, היה צורך ברכיבי event listener מורכבים לאירועי גלילה, בחישובים ידניים של היסט הגלילה ובצופים בצומת. האירוע scrollsnapchange מספק דרך יעילה ומובנית לזהות מתי סרגל גלילה התייצב על יעד הצמדה חדש, וכך עוזר לסנכרן סרגלי צד או להדגיש את הקטע הפעיל בתוכן העניינים.

הטמעה

1. הגדרת גלילה עם נקודות עצירה (scroll snapping) ב-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));
}