Scroll snapping
Scroll snapping is a ScrollTrigger feature that eases the scroll position to the nearest defined stop after the user stops scrolling. You pass a `snap` value on a scrubbed ScrollTrigger, and instead of parking mid-animation, the page settles to a clean progress increment, a specific value, or a timeline label. It turns a free-scrolling section into one that lands on the states you designed.
Updated July 1, 2026
How scroll snapping works
Snapping only makes sense on a scrubbed ScrollTrigger, one where scrub ties animation progress to scroll position. Progress runs from 0 at start to 1 at end. The snap config watches for the user to stop scrolling, then tweens the scroll position so progress lands exactly on the nearest snap target you defined.
The snap value takes several shapes. A number is a repeating increment (1 / 3 snaps to quarters of the progress). An array snaps to specific progress values. "labels" snaps to the labels on the linked timeline, which is the cleanest option when the section is a labelled timeline of named states.
Tuning the snap object
Passing a bare number or "labels" uses default snap timing. For control over how the settle feels, pass an object. snapTo holds the same value shapes as above, and the rest tune the tween that moves the scrollbar.
snapTochooses the targets: an increment number, an array of values, or"labels"for timeline labelsdurationsets how long the scroll settles, keep it short (0.3 to 0.5) so it reads as a snap, not a scrolldelayis the pause after scrolling stops before the snap fires, useful so a mid-scroll hesitation does not trigger iteaseshapes the settle,power1.inOutfeels neutral, expo-family eases feel more mechanical here
Snap needs scrub
toggleActions trigger, there is no continuous progress to land on. It also does not work on a containerAnimation-based ScrollTrigger (fake horizontal scroll). If snap looks ignored, check that the trigger is scrubbed and not container-driven.Use scroll snapping for
- Pinned horizontal panel sections where each panel should come to rest fully in view
- Scroll-through step sequences (a labelled timeline of named states you never want parked half-way)
- Full-height scrollytelling sections where the reader should land on one scene at a time
- Scrubbed product tours where each stop is a distinct configuration the reader needs to read
Use something else when
- Snapping whole pages or full-viewport sections with no scrubbed animation, native CSS
scroll-snap-type/scroll-snap-alignis lighter and needs no JS - A carousel or slider snapping to slides on drag, use Draggable with inertia snapping (its own
snap), not ScrollTrigger - A section that should scrub freely with no forced stops, drop
snapentirely and letscrubrun open - Simple anchor navigation to a section,
ScrollToPluginorscrollIntoViewis the right tool, not scroll snapping
Native CSS scroll snapping is worth reaching for first when the effect is plain page-to-page or slide-to-slide settling with no linked animation. The CSS scroll primitives keep growing: CSS scroll-driven animations reached about 85% browser support as of Safari 26 in September 2025 (caniuse, 2025). Reach for ScrollTrigger's snap specifically when the stops are tied to animation progress or timeline labels, which native CSS cannot express.
Used in these Annnimate components
Both of these show the snap idea in practice, even where the mechanism differs from the ScrollTrigger snap option:
- The Infinite Parallax Slider settles to the nearest slide after a drag ends, the same 'release and let it land on a clean stop' behavior that snap adds to a scrubbed scroll section
- The Image Dissolve Scroll scrubs a shader dissolve across its scroll range, the kind of pinned scrubbed section where a progress-increment snap keeps the reader from parking mid-dissolve
Snap after you pin
"labels". That order (pin, scrub, label, snap) is the pattern behind most scroll-through sequences on award sites.See it running in production
Common questions
- Why is my ScrollTrigger snap being ignored?
- Snap only works on a scrubbed ScrollTrigger. If you are using
toggleActionsinstead ofscrub, there is no continuous progress for it to land on, sosnapdoes nothing. It also does not work on a trigger driven bycontainerAnimation(fake horizontal scroll). Addscrub: 1and make sure the trigger is not container-based. - What's the difference between snap: 0.25 and snap: "labels"?
snap: 0.25snaps to evenly spaced progress increments (0, 0.25, 0.5, 0.75, 1).snap: "labels"snaps to the labels you added on the linked timeline withaddLabel(), so the stops match your named states even if they are unevenly spaced. Use the number for uniform panels, labels for a hand-authored step sequence.- Should I use ScrollTrigger snap or native CSS scroll-snap?
- Use native CSS
scroll-snap-typewhen you just need pages or slides to settle into place with no linked animation, it is lighter and needs no JS. Use ScrollTrigger'ssnapwhen the stops are tied to scrubbed animation progress or timeline labels, which CSS scroll-snap cannot reach into. - How do I stop the snap from feeling abrupt?
- Pass
snapas an object and tune it:{ snapTo: "labels", duration: 0.4, delay: 0.05, ease: "power1.inOut" }. Keepdurationshort (0.3 to 0.5) so it reads as a settle, add a smalldelayso a mid-scroll pause does not trigger it early, and use a neutral ease likepower1.inOut. - Does scroll snapping work with a smooth-scroll library like Lenis?
- It can, but you have to route ScrollTrigger through the smooth scroller correctly (via
ScrollTrigger.scrollerProxyor the library's GSAP integration) and let the scroller own the scroll position. Snapping and smooth-scroll both write to scroll position, so if they fight you get jitter. Test the snap settle specifically once smooth scroll is wired in.
self.getVelocity() inside its callbacks.