Scroll

Scrub

Scrub is the ScrollTrigger option that binds a tween's progress to scroll position instead of time. The animation no longer plays on its own; it advances frame-by-frame as the user scrolls, and rewinds as they scroll back. Pass `scrub: true` for instant 1:1 binding or `scrub: <seconds>` to add a catch-up lag that smooths jitter.

Updated June 1, 2026

Mechanics

How scrub works

Without scrub, ScrollTrigger fires onEnter and the tween plays at its own duration. With scrub, the tween's progress (0->1) is mapped directly to scroll progress between start and end. Scroll forward, the tween advances; scroll back, it reverses. The tween's duration becomes meaningless - scroll distance is the new clock.

script.js
Smoothing

scrub: true vs scrub: number

scrub: true is exact 1:1 binding. Every scroll delta moves the tween. Touchpads, mouse-wheel ticks, and orientation changes all show up as visible jitter on the animated element.

scrub: 1 adds 1 second of catch-up lag. The tween's target progress is still scroll position, but it eases toward it over a second. The result feels cinematic and forgives input noise. Most production sites use values between 0.5 and 1.5.

Tradeoff

Higher scrub values look smoother but introduce a visible lag between the user's gesture and the animation. If the animation IS the feedback (a counter, a progress bar), keep scrub low. If it's a decorative reveal, push it higher.
When

Use scrub for

  • Parallax effects where layers move at different rates with scroll
  • Scroll-linked progress indicators (bars, counters, percentage badges)
  • Pinned scenes where inner content advances with scroll position
  • Reveal sequences where exact scroll-position pacing matters more than fixed timing
  • WebGL uniforms (color, dissolve, deformation) that should track scroll
Alternatives

Use something else when

  • Fire-and-forget reveals on enter - use toggleActions: 'play none none reverse' instead
  • One-shot animations that should play at their own speed regardless of scroll - omit scrub
  • Animations that should fire ONCE and never reverse - use once: true
  • Tight UI feedback (hover, click) where binding to scroll makes no sense
In production

Used in these Annnimate components

  • The Parallax component scrubs layer transforms across the trigger range so each layer moves at its own rate
  • The Progress component scrubs a width tween so the bar grows in lockstep with page scroll
  • The Velocity Clip component scrubs a clip-path tween across the section so the reveal tracks scroll position
  • The Image Dissolve Scroll shader scrubs the dissolve uniform 0->1 across the pinned section
Used in components

See it running in production

FAQ

Common questions

What's the difference between scrub: true and scrub: 1?
scrub: true is instant 1:1 binding - every scroll delta moves the tween that frame. scrub: 1 adds a 1-second smoothing lag - the tween eases toward its target progress over a second. Both bind to scroll position; the number adds forgiveness for touchpad/jitter input.
Why does my scrubbed animation feel laggy?
Either you set scrub too high (try 0.5 instead of 2), or your animation is doing layout work each frame (animating width/height/top). Stick to GPU properties: x, y, scale, opacity, clipPath. Layout properties on scrub multiply the cost across every scroll tick.
Can I scrub a timeline?
Yes - the scrub option works on timeline ScrollTriggers the same way. The whole timeline's progress (0->1 across all its tweens) becomes scroll-bound. Position parameters and per-tween eases still apply within that timeline's normalized progress.
Does scrub work with ease?
Sort of. The TWEEN'S ease is bypassed under scrub (you're now driving by position, not time), but the SCRUB itself has its own ease curve - scrub: 1 is approximately ease-out at the smoothing layer. For per-tween easing under scrub, use gsap.timeline() with eased child tweens and scrub the timeline.
What's the ideal scrub value?
Depends on intent. true for instant feedback (progress bars, counters where the number is the feedback). 0.5-1 for cinematic reveals and parallax. 1.5-2 for atmospheric effects where exact sync doesn't matter. Anything above 2 starts to feel disconnected from the gesture.