Tradicionalmente, para sincronizar el estado de la IU con la posición de ajuste de un contenedor desplazable, se necesitaban objetos de escucha de eventos de desplazamiento complejos, cálculos manuales de los desplazamientos y observadores de intersección. El evento scrollsnapchange proporciona una forma nativa y eficiente de detectar cuándo un elemento de desplazamiento se estableció en un nuevo objetivo de ajuste, lo que lo hace útil para sincronizar barras laterales o destacar la sección activa en un índice.
Implementación
1. Configura el ajuste de desplazamiento en CSS
El contenedor debe tener definido scroll-snap-type y tener elementos secundarios con scroll-snap-align para que el navegador haga un seguimiento de los objetivos de ajuste. En un artículo largo con un índice, puedes usar esta propiedad para ajustar los encabezados de las secciones a la parte superior del viewport.
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. Escucha los cambios de instantáneas
Usa el evento scrollsnapchange en el contenedor de desplazamiento para reaccionar cuando el usuario termine de desplazarse y el navegador se ajuste a un elemento nuevo. En nuestra demostración de TOC, usamos esto para destacar el vínculo activo en la barra lateral.
<!-- 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);
}
});
Accesibilidad
En el caso de un índice, asegúrate de que los vínculos de la barra lateral usen aria-current="true" o aria-current="location" cuando estén activos.
Además, ten cuidado cuando uses el valor mandatory para scroll-snap-type, ya que puede hacer que el contenido entre los puntos de ajuste se vuelva inaccesible cuando sea más largo que la pantalla.
Estrategias de resguardo
Si no se admite scrollsnapchange, usa IntersectionObserver para detectar qué elemento se encuentra actualmente en la parte superior del desplazador. Ten en cuenta que este comportamiento es diferente al de scrollsnapchange, ya que se activará mientras se produce el desplazamiento, en lugar de solo cuando se haya establecido.
// 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));
}