Synchronizacja stanu przyciągania podczas przewijania

Synchronizowanie stanu interfejsu z pozycją przyciągania kontenera przewijanego tradycyjnie wymagało złożonych detektorów zdarzeń przewijania, ręcznych obliczeń przesunięć przewijania i obserwatorów przecięcia. Zdarzenie scrollsnapchange zapewnia natywny i wydajny sposób wykrywania, kiedy przewijany element zatrzymał się na nowym celu przyciągania, co jest przydatne do synchronizowania pasków bocznych lub wyróżniania aktywnej sekcji w spisie treści.

Implementacja

1. Konfigurowanie przewijania z przyciąganiem w CSS

Aby przeglądarka mogła śledzić cele przyciągania, kontener musi mieć zdefiniowaną właściwość scroll-snap-type i zawierać elementy podrzędne z właściwością scroll-snap-align. W długim artykule ze spisem treści możesz użyć tej funkcji, aby przyciągnąć nagłówki sekcji do góry widocznego obszaru.

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. Nasłuchiwanie zmian w przyciąganiu

Użyj zdarzenia scrollsnapchange w kontenerze przewijania, aby reagować, gdy użytkownik zakończy przewijanie, a przeglądarka przejdzie do nowego elementu. W naszej wersji demonstracyjnej spisu treści używamy tego, aby wyróżnić aktywny link na pasku bocznym.

<!-- 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);
  }
});

Ułatwienia dostępu

W przypadku spisu treści upewnij się, że linki na pasku bocznym używają atrybutu aria-current="true" lub aria-current="location", gdy są aktywne.

Zachowaj też ostrożność, gdy używasz wartości mandatory dla scroll-snap-type, ponieważ może to spowodować, że treści między punktami przyciągania staną się niedostępne, jeśli będą dłuższe niż ekran.

Strategie zastępcze

Jeśli właściwość scrollsnapchange nie jest obsługiwana, użyj IntersectionObserver, aby wykryć, który element znajduje się obecnie na górze przewijaka. Pamiętaj, że to zachowanie różni się od scrollsnapchange, ponieważ zostanie aktywowane podczas przewijania, a nie tylko po jego zakończeniu.

// 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));
}