A React provider that wires Lenis smooth scroll into GSAP and ScrollTrigger.
GSAP with scroll-based animations has its own requestAnimationFrame loop that updates on every frame. If you're using Lenis Scroll, it does the same on parallel. Since they run on different loops, you may end up with different animations running at different speeds, causing a jitter effect.
This provider solves this issue synchronizing the two loops, so that the animations run at the same speed.
If you haven't already, install the dependencies:
npm install gsap @gsap/react lenisbun add gsap @gsap/react lenisdeno add gsap @gsap/react lenispnpm add gsap @gsap/react lenisyarn add gsap @gsap/react lenisCopy and paste the following code into your project.
"use client";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import type { LenisOptions } from "lenis";
import { type LenisRef, ReactLenis } from "lenis/react";
import { useEffect, useRef } from "react";
gsap.registerPlugin(ScrollTrigger);
type GsapLenisProviderProps = (
| {
global?: never;
children: React.ReactNode;
}
| {
global: true;
children?: never;
}
) & { className?: string } & LenisOptions;
function GsapLenisProvider({
global,
children,
className,
...options
}: GsapLenisProviderProps) {
const lenisRef = useRef<LenisRef>(null);
useEffect(() => {
let cancelled = false;
let updateGsap: ((time: number) => void) | null = null;
function init() {
const lenis = lenisRef.current?.lenis;
if (!lenis) {
if (!cancelled) {
requestAnimationFrame(init);
}
return;
}
if (cancelled) {
return;
}
lenis.on("scroll", ScrollTrigger.update);
updateGsap = (time: number) => {
lenis.raf(time * 1000);
};
gsap.ticker.add(updateGsap);
gsap.ticker.lagSmoothing(0);
}
init();
return () => {
cancelled = true;
const lenis = lenisRef.current?.lenis;
if (lenis) {
lenis.off("scroll", ScrollTrigger.update);
}
if (updateGsap) {
gsap.ticker.remove(updateGsap);
}
};
}, []);
if (global) {
return (
<ReactLenis
className={className}
options={{ autoRaf: false, ...options }}
ref={lenisRef}
root
/>
);
}
return (
<ReactLenis
className={className}
options={{ autoRaf: false, ...options }}
ref={lenisRef}
>
{children}
</ReactLenis>
);
}
export default GsapLenisProvider;You can set a global provider to make it available across your entire application:
import GsapLenisProvider from "@/providers/gsap-lenis-provider";
export default function Home() {
return (
<main>
<GsapLenisProvider global options={{ smoothWheel: true, ... }} />
<div>Hello World</div>
</main>
)
}Or you can use it as a local provider to wrap a specific component:
import GsapLenisProvider from "@/providers/gsap-lenis-provider";
export default function Home() {
return (
<GsapLenisProvider className="flex flex-col">
<div>Hello World</div>
</GsapLenisProvider>
)
}