From Chrome 129 you can use the scrollSnapChange
and scrollSnapChanging
events from JavaScript. By implementing built-in snap events, the previously invisible snap state will become actionable, at the right time, and always correct. This is not a convenience you had without these events.
Before scrollSnapChange
, you could use an intersection observer to find what element was crossing the scroll port, but determining what was snapped was restricted to a few circumstances. For example, you can detect if the snap items fill the scroll port or fill the majority of the scroll port. To do this you'd observe intersecting elements of the scroll area, then based on which item is consuming the majority of the scroll area, assume this was the snap target, then wait for scrollend
and act upon the item snapped (the snap target).
Before scrollSnapChanging
however, knowing when the snap target is changing or what it's being changed to (like given a scroll fling) was impossible.
Great news, these new events make this information available quickly and easily. This unlocks scroll snap interactions to reach beyond their current capability and enables the orchestration of scroll snap relationships and fresh UI feedback scenarios.
scrollSnapChange
This event fires only if a scroll gesture has resulted in a new snap target having been rested on, and in the following order
- After the scroll has rested.
- Before
scrollend
.
This event fires just before scrollend
, when scrolling is complete, and only if a new snap target was rested upon. This makes the event feel lazy or just-in-time when the scroll gesture has completed.
scroller.addEventListener('scrollsnapchange', event => {
console.log(event.snapTargetBlock);
console.log(event.snapTargetInline);
})
scroller.onscrollsnapchange = event => {
console.log(event.snapTargetBlock);
console.log(event.snapTargetInline);
}
The event exposes the snapped item on the event object as snapTargetBlock
and snapTargetInline
. If the scroller is horizontal only, the snapTargetBlock
property will be null
. The value of the property will be the element node.
Unique details for scrollSnapChange
Does not fire until the user releases their gesture
A finger still on the screen or fingers on a trackpad indicate the user's gesture is not finished scrolling, which means scroll has not ended, which means the snap target hasn't changed yet, it's pending a complete user gesture.
Does not fire if the snap target did not change
The event is for snap change, and if the snap target did not change, the event won't fire, even if the scroller was interacted with by a user. The user did in fact scroll though, so upon scroll completion the scrollend
event still fires.
scrollSnapChanging
This event fires as soon as the browser has decided that the scroll gesture has or will result in a new snap target. It fires eagerly, and during scrolling.
scroller.addEventListener('scrollsnapchanging', event => {
console.log(event.snapTargetBlock);
console.log(event.snapTargetInline);
})
scroller.onscrollsnapchanging = event => {
console.log(event.snapTargetBlock);
console.log(event.snapTargetInline);
}
The event exposes the snapped item on the event object as snapTargetBlock
and snapTargetInline
. If the scroller is vertical only, the snapTargetInline
property will be null
. The value of the property will be the element node.
Unique details for scrollSnapChanging
Fires early and often during a scroll gesture
Unlike scrollSnapChange
which awaits a complete user scroll gesture, this event will fire eagerly while a user scrolls with their finger or using a trackpad. Consider a user who is scrolling slowly without lifting a finger, scrollSnapChanging
will fire multiple times during the gesture as long as the user is panning over multiple potential snap targets. Each time the user scrolls, if the browser has determined that upon release it would rest upon a new snap target, the event fires to tell you which element that is.
Does not fire all the snap targets along the way to a new snap target
Furthermore, consider a fling, where a user does a scroll throw gesture that spans multiple snap targets at once; this event will fire once with the target that will be rested upon. So it's eager but not wasteful, supplying you with the snap target as soon as possible.
Use cases and examples
These events enable many new use cases while also making current patterns much easier to implement. One prime example is enabling snap triggered animation. Contextually revealing the snap item, the snap item's children or associated information when it is the snap target.
The following patterns demonstrate some use cases to help you be productive straight away.
Highlight a testimonial
This example promotes or visually focuses the snapped testimonial.
scroller.onscrollsnapchange = event => {
scroller.querySelector(':scope .snapped')?.classList.remove('snapped')
event.snapTargetInline.classList.add('snapped')
}
Show the caption for the snapped item
This example shows the caption for the snapped item. Both events are included in this demo, so you can see the timing and user experience differences between scrollSnapChange
and scrollSnapChanging
.
Animate once, the children of a snapped presentation slide
This example knows when a new slide has been landed and rested on, which is a great time to animate the contents once.
document.addEventListener('scrollsnapchange', event => {
event.snapTargetBlock.classList.add('seen')
})
Snapping on both x and y in a scroller
Scroll snap works for scrollers that allow horizontal and vertical scrolling. This demo shows both scrollSnapChanging
and scrollSnapChange
targets as you scroll around the grid. This demo illuminates how the element the browser is snapping to may not always be the one you think it is.
Two linked scrollers
This demo has two scroll snapping containers where one is a high level list of links and the other is the actual paged content. The new scrollSnapChanging
event makes it trivial to link the snap states of these bi-directionally so they're always in sync.
OKLCH color picker
This demo has 3 scrollers, each representing a different color channel in OKLCH. The snapped item is synchronized with its relevant radio group and the data can be retrieved from a form wrapping the inputs. To a mouse or touch user, you can scroll to the value you want. For keyboard users, you can tab and use the arrow keys. For a screen reader, it's just a form.
Snapping staggering animated hubs
This demo progressively enhances the scroll snapping experience with snap triggered transitions using scrollsnapchange
.
Check for event support with the following JavaScript:
if ('onscrollsnapchange' in window) {
// ok to use snap change
}
Scrollable ruler input
This demo features a scrollable ruler as an alternative way to pick a length for a number input. Enter values directly into the number input or scroll to the size. The changing event is used to clear the selection during the user's gesture, while the change event is used to update state and reinforce the user's choice.
Cover flow
This demo builds upon Bramus Van Damme's excellent scroll driven animation recreation of the famous macOS cover flow (video tutorial too). Uniquely, scrollSnapChanging
is used to hide the album title and scrollSnapChange
is used to present the title. The events help orchestrate an eager hide of the old title and a lazy presentation of the new one.
More ideas to inspire some creativity
Now that it's trivial to know which element is about to be snapped and which is actively snapped, there are many new possibilities! Here's a list of ideas to help inspire creativity and additional use cases:
- Triggering lazy loading, known as snapchange triggered rendering or data fetching.
- Film strip thumbnails linked to a larger image.
- Toggling play/pause for a video trailer for a snapped video thumbnail.
- Analytics tracking
- Scrollytelling
- Wheel of Fortune UI/UX
- Snap target gets an anchored tooltip.
- Tap to snap
- Snap to reveal
- Sounds on snap
- Swipe UI
- Swipeable tabs or carousels
Further studies
The Chrome team is excited to hear what you build with these new APIs and hope it helps streamline your scrollable experiences.