Journal
Lenis + ScrollTrigger: smooth scroll without broken pins
Lenis smooths the scroll, and ScrollTrigger runs animations at scroll positions, so ScrollTrigger must be told every time Lenis moves the page, and both must share one clock. Connect them with four lines (shown below), start Lenis only when the visitor has not asked for reduced motion, and call ScrollTrigger.refresh() after the layout changes.
Why they need to be connected
Lenis takes over how the page scrolls: instead of jumping with each wheel tick, the scroll position eases toward its target. ScrollTrigger reads the scroll position to decide when an animation starts, ends or pins. If ScrollTrigger is not told when Lenis has moved the page, it can read stale positions, and pinned sections jump, flicker or release at the wrong point.
The fix is to make Lenis tell ScrollTrigger on every scroll update, and to drive Lenis from GSAP's own ticker so there is one animation clock, not two.
The connection, in four lines
const lenis = new Lenis();
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);- lenis.on("scroll", ScrollTrigger.update): ScrollTrigger re-reads the position every time Lenis scrolls.
- gsap.ticker.add(...): GSAP's ticker calls Lenis once per frame. GSAP's ticker time is in seconds and Lenis expects milliseconds, hence time * 1000.
- gsap.ticker.lagSmoothing(0): stops GSAP from smoothing over long frames, which would make Lenis and ScrollTrigger disagree after a hiccup.
- Do not also call lenis.raf from requestAnimationFrame. Two loops driving one scroller is the most common cause of jitter.
This is the integration documented by Lenis for use with GSAP. Options and method names can change between versions, so check the Lenis README for the version you install. Our library uses Lenis 1.3 and GSAP 3.15.
What a correct setup looks like in practice
Our demo host does exactly this. When a demo asks for smooth scrolling and the visitor has not asked for reduced motion, it creates Lenis, wires it to ScrollTrigger, drives it from gsap.ticker, turns lag smoothing off, and adds a "lenis" class to the page. When reduced motion is on, none of that starts and the page scrolls normally. It also tells ScrollTrigger to ignore the mobile browser bar resizing, which otherwise makes pinned sections jump on phones.
49 of the 50 pages in our library that use Lenis are full templates, for example the restaurant, café and bakery templates and the real estate templates. See the full Lenis smooth scroll examples for the list.
Seven mistakes that break pins
- Skipping the connection. Without lenis.on("scroll", ScrollTrigger.update), triggers fire late or not at all.
- Two loops. Calling lenis.raf from requestAnimationFrame and from gsap.ticker. Use only the ticker.
- Forgetting lag smoothing. Leave it on and a slow frame makes the animation catch up in a visible jump.
- Not refreshing. After fonts load, images load, or the layout changes, call ScrollTrigger.refresh() so start and end positions are recalculated. Pinned sections are the first thing to look wrong.
- Anchor links that bypass Lenis. A normal #anchor jump ignores the smoothing. Use lenis.scrollTo for in-page links.
- Scrollable areas inside the page. A modal, a menu or a code box with its own scroll will be fought over by Lenis. Mark it with the data-lenis-prevent attribute, and call lenis.stop() while a full-screen overlay is open.
- Smooth scroll on when the visitor asked for less motion. Do not start Lenis at all when prefers-reduced-motion is reduce.
How to test it
- Scroll slowly and quickly through every pinned section, up and down. Nothing should jump or flicker.
- Resize the window, or rotate a phone, while a section is pinned.
- Use the keyboard (space, arrow keys, page up and down) and click an in-page link.
- Turn on reduce motion in your device settings and reload: the page should scroll natively and still show everything.
- On a phone, scroll with a finger and watch the browser bar hide and show.
ScrollTrigger can draw its start and end markers on the page while you debug, which makes a wrong trigger obvious. Our demos accept a markers option for the same reason. If an AI tool wired this up for you and something is off, the help page has a ready fix prompt for "the animation doesn't play". The wider ScrollTrigger picture is in Best GSAP ScrollTrigger effects for landing pages.
When not to use smooth scroll
- Dense application screens where precise, native scrolling matters.
- Pages for visitors who often use assistive technology or have asked for less motion.
- Any page where it makes the scroll feel slower than the content deserves.
Smooth scrolling is a taste decision. It suits editorial and portfolio pages, and many people find it nicer, but it is never required for ScrollTrigger to work.
Questions, answered
Do I need Lenis to use ScrollTrigger?
No. ScrollTrigger works with the normal browser scroll. Lenis only adds smoothing, and when you use both they must be connected.
Why do my pinned sections jump with Lenis?
Usually ScrollTrigger is not being updated on each Lenis scroll, two loops are driving Lenis, lag smoothing is on, or ScrollTrigger.refresh() was not called after the layout changed.
Should I disable Lenis for reduced motion?
Yes. Do not start it when prefers-reduced-motion is reduce, and let the page scroll natively.
Is Lenis free?
Lenis is an open-source library under an MIT licence. Check its repository for the current terms.
Where this comes from
- Lenis documentation and README (integration with GSAP ScrollTrigger)
- GSAP ScrollTrigger documentation (refresh, update, config)
- MOTION/LAB demo host runtime (our own code): Lenis 1.3 and GSAP 3.15