Fixing Frozen GSAP Animations: The Lenis ScrollTrigger Ticker Fix
Your GSAP ScrollTrigger animations stuttering with Lenis smooth scroll? Learn why scrubbed scenes freeze and how a 4-line `gsap.ticker.add` patch restores smooth, 60fps motion.
The Problem: Why Lenis and ScrollTrigger Don't Sync Natively
If you build modern websites, you've likely combined GreenSock (GSAP) for animations and a library like Lenis for smooth scrolling. GSAP's ScrollTrigger plugin is powerful for creating animations that react to scroll position. Lenis provides a polished, fluid scroll experience that native browser scrolling often lacks.
The problem arises when you use them together, especially with scrub animations—where the animation's progress is directly tied to the scrollbar's position. You'll notice the animation becomes jerky, stutters, or freezes entirely. This happens because of a fundamental conflict in how they operate.
- GSAP ScrollTrigger by default listens to the browser's native
scrollevent. - Lenis disables native scroll behaviour and creates its own smooth scroll loop using
requestAnimationFrame. It calculates the desired scroll position and manually updates the page style, creating the smooth effect.
Because Lenis controls the scroll, the native scroll event that ScrollTrigger is listening for doesn't fire consistently or in sync with what the user actually sees. This desynchronization is what causes the animation to break. To solve this, we need a reliable Lenis ScrollTrigger gsap.ticker fix.
The Solution: The gsap.ticker.add Fix
The correct way to synchronize these two libraries is to hook Lenis's update loop directly into GSAP's central update mechanism, known as the ticker. GSAP's ticker is a requestAnimationFrame loop that drives every GSAP animation on the page. By adding Lenis to this ticker, you ensure both libraries are operating on the exact same frame, every frame.
Here is the essential code snippet:
const lenis = new Lenis()
lenis.on('scroll', ScrollTrigger.update)
gsap.ticker.add((time)=>{
lenis.raf(time * 1000)
})
gsap.ticker.lagSmoothing(0)
Let's break this down:
const lenis = new Lenis(): We initialize Lenis as usual.lenis.on('scroll', ScrollTrigger.update): This line tells ScrollTrigger to run its update function whenever Lenis emits ascrollevent. This helps with basic trigger points but is often insufficient for perfectly smoothscrubanimations.gsap.ticker.add((time)=>{ lenis.raf(time * 1000) }): This is the core of the fix. We add a function to GSAP's ticker. On every tick (i.e., every frame), this function calls Lenis'sraf(requestAnimationFrame) method, passing it the current time. This makes GSAP the master clock, and Lenis updates its scroll position in perfect harmony.gsap.ticker.lagSmoothing(0): This is an optional but highly recommended line. It disables GSAP's lag smoothing feature, which can sometimes cause jumps as it tries to compensate for dropped frames. With a smooth scroll library like Lenis managing the experience, this feature is unnecessary and can interfere.
Real-World Implementation at JRV Systems
We apply this technique in projects that require a premium user experience. For example, on a recent website for a Malaysian property developer, we used scroll-driven animations to unveil floor plans and showcase architectural features as the user scrolled down the page. Initially, without the ticker integration, the experience on high-refresh-rate monitors was noticeably jittery.
Implementing the Lenis ScrollTrigger gsap.ticker fix immediately resolved the issue. The animations became perfectly smooth, creating the seamless, high-end feel the client wanted. This small piece of code is a non-negotiable part of our front-end stack whenever these two libraries are used together.
Important Considerations: iOS and Accessibility
While this fix is robust, there are two important factors to keep in mind for a production-ready site.
iOS Safari Edge Cases
Mobile Safari has its own unique ways of handling scroll events and conserving battery, which can sometimes affect requestAnimationFrame timing. The gsap.ticker fix works reliably on iOS, but it is always critical to test your scroll animations on a physical iPhone. Pay close attention to performance during fast scrolls or on content-heavy pages.
Respecting User Preferences
Some users enable prefers-reduced-motion in their operating system settings due to vestibular disorders or personal preference. Forcing a smooth, motion-heavy scroll experience on them is poor practice. We should always check for this setting and disable Lenis if it's active.
- Conditionally initialize Lenis:
let lenis;
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
lenis = new Lenis()
// ... add the ticker fix here
}
This ensures that users who prefer less motion get a standard, native scrolling experience, which is an important accessibility win.
How to Confirm the Fix is Working
Don't just trust your eyes. Use your browser's developer tools to get objective data on performance.
- Open Chrome DevTools.
- Press
Command+Shift+P(Mac) orControl+Shift+P(Windows) to open the Command Menu. - Type "Show Rendering" and press Enter.
- In the Rendering tab that appears, check the "Frame Rendering Stats" box.
An overlay will appear in the corner of your screen. When you scroll, watch the "Frames per second" (FPS) reading. Before the fix, you'll likely see erratic numbers and frequent dips below 60 FPS. After applying the gsap.ticker fix, you should see a stable reading, typically locked at 60 FPS (or higher, depending on your monitor's refresh rate). This is your confirmation that the animation is rendering smoothly on every frame.