Scroll

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

Mechanics

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.

script.js

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.

script.js
Config

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.

script.js
  • snapTo chooses the targets: an increment number, an array of values, or "labels" for timeline labels
  • duration sets how long the scroll settles, keep it short (0.3 to 0.5) so it reads as a snap, not a scroll
  • delay is the pause after scrolling stops before the snap fires, useful so a mid-scroll hesitation does not trigger it
  • ease shapes the settle, power1.inOut feels neutral, expo-family eases feel more mechanical here

Snap needs scrub

Snapping does nothing on a 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.
When

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
Alternatives

Use something else when

  • Snapping whole pages or full-viewport sections with no scrubbed animation, native CSS scroll-snap-type / scroll-snap-align is 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 snap entirely and let scrub run open
  • Simple anchor navigation to a section, ScrollToPlugin or scrollIntoView is 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.

In production

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

The strongest snapping sections are pinned first. Pin the section, scrub a timeline through it, add labels for each state, then snap to "labels". That order (pin, scrub, label, snap) is the pattern behind most scroll-through sequences on award sites.
Used in components

See it running in production

FAQ

Common questions

Why is my ScrollTrigger snap being ignored?
Snap only works on a scrubbed ScrollTrigger. If you are using toggleActions instead of scrub, there is no continuous progress for it to land on, so snap does nothing. It also does not work on a trigger driven by containerAnimation (fake horizontal scroll). Add scrub: 1 and make sure the trigger is not container-based.
What's the difference between snap: 0.25 and snap: "labels"?
snap: 0.25 snaps 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 with addLabel(), 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-type when you just need pages or slides to settle into place with no linked animation, it is lighter and needs no JS. Use ScrollTrigger's snap when 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 snap as an object and tune it: { snapTo: "labels", duration: 0.4, delay: 0.05, ease: "power1.inOut" }. Keep duration short (0.3 to 0.5) so it reads as a settle, add a small delay so a mid-scroll pause does not trigger it early, and use a neutral ease like power1.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.scrollerProxy or 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.