55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Lenis smooth scroll wired into GSAP ScrollTrigger.
|
|
* - Lenis drives the rAF loop; ScrollTrigger.update() runs on every Lenis tick
|
|
* so pinned/scrubbed animations stay in lockstep with the smoothed scroll.
|
|
* - Disabled entirely when prefers-reduced-motion is set (native scroll).
|
|
* - Exposes a scroll-progress value on :root for the top progress bar (CSS).
|
|
*/
|
|
import { useEffect } from "react";
|
|
import Lenis from "lenis";
|
|
import { gsap, ScrollTrigger } from "./gsap";
|
|
|
|
export default function SmoothScroll() {
|
|
useEffect(() => {
|
|
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
|
|
// Always keep the CSS progress var fed (cheap), even without Lenis.
|
|
const setProgress = (p: number) =>
|
|
document.documentElement.style.setProperty("--scroll-progress", String(p));
|
|
|
|
if (reduce) {
|
|
const onScroll = () => {
|
|
const h = document.documentElement;
|
|
const max = h.scrollHeight - h.clientHeight;
|
|
setProgress(max > 0 ? h.scrollTop / max : 0);
|
|
};
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
onScroll();
|
|
return () => window.removeEventListener("scroll", onScroll);
|
|
}
|
|
|
|
const lenis = new Lenis({
|
|
duration: 1.1,
|
|
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
|
|
smoothWheel: true,
|
|
});
|
|
|
|
lenis.on("scroll", (e: { progress: number }) => {
|
|
ScrollTrigger.update();
|
|
setProgress(e.progress);
|
|
});
|
|
|
|
const raf = (time: number) => lenis.raf(time * 1000);
|
|
gsap.ticker.add(raf);
|
|
gsap.ticker.lagSmoothing(0);
|
|
|
|
return () => {
|
|
gsap.ticker.remove(raf);
|
|
lenis.destroy();
|
|
};
|
|
}, []);
|
|
|
|
return null;
|
|
}
|