A React provider that integrates Lenis smooth scroll with Motion, so scroll-linked animations stay in sync.
Motion 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 motion lenisbun add motion lenisdeno add motion lenispnpm add motion lenisyarn add motion lenisCopy and paste the following code into your project.
"use client";
import type { LenisOptions } from "lenis";
import { type LenisRef, ReactLenis } from "lenis/react";
import { cancelFrame, frame } from "motion/react";
import { useEffect, useRef } from "react";
type MotionLenisProviderProps = (
| {
global?: never;
children: React.ReactNode;
}
| {
global: true;
children?: never;
}
) & { className?: string } & LenisOptions;
function MotionLenisProvider({
global,
children,
className,
...options
}: MotionLenisProviderProps) {
const lenisRef = useRef<LenisRef>(null);
useEffect(() => {
function update(data: { timestamp: number }) {
lenisRef.current?.lenis?.raf(data.timestamp);
}
frame.update(update, true);
return () => cancelFrame(update);
}, []);
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 MotionLenisProvider;You can set a global provider to make it available across your entire application:
import MotionLenisProvider from "@/providers/motion-lenis-provider";
export default function Home() {
return (
<main>
<MotionLenisProvider global options={{ smoothWheel: true, ... }} />
<div>Hello World</div>
</main>
)
}Or you can use it as a local provider to wrap a specific component:
import MotionLenisProvider from "@/providers/motion-lenis-provider";
export default function Home() {
return (
<MotionLenisProvider className="flex flex-col">
<div>Hello World</div>
</MotionLenisProvider>
)
}