Kumasy-Marketing/app/components/smooth-scroll.tsx
2026-09-20 01:52:28 +02:00

65 lines
1.8 KiB
TypeScript

"use client";
import { useEffect } from "react";
import Lenis from "lenis";
import { useReducedMotion } from "./motion";
/**
* Momentum scrolling. The wheel no longer maps 1:1 to scroll position —
* it feeds a spring that eases toward the target, so every scroll-linked
* animation on the page inherits the same softened motion.
*
* Disabled entirely under prefers-reduced-motion: hijacking the scroll is
* exactly what that preference asks us not to do.
*/
export function SmoothScroll() {
const reduced = useReducedMotion();
useEffect(() => {
if (reduced) return;
const lenis = new Lenis({
// Slightly longer than default so the easing is felt, not just seen.
duration: 1.1,
easing: (t: number) => 1 - Math.pow(1 - t, 3),
// Touch devices keep their native scrolling; overriding it there
// fights the platform and breaks momentum handoff.
smoothWheel: true,
touchMultiplier: 1.6,
});
let frame = 0;
const raf = (time: number) => {
lenis.raf(time);
frame = requestAnimationFrame(raf);
};
frame = requestAnimationFrame(raf);
// In-page anchors must go through Lenis, or they jump past the easing.
const onClick = (event: MouseEvent) => {
const anchor = (event.target as HTMLElement | null)?.closest?.(
'a[href^="#"]',
) as HTMLAnchorElement | null;
if (!anchor) return;
const id = anchor.getAttribute("href");
if (!id || id === "#") return;
const target = document.querySelector(id);
if (!target) return;
event.preventDefault();
lenis.scrollTo(target as HTMLElement, { offset: -104 });
};
document.addEventListener("click", onClick);
return () => {
document.removeEventListener("click", onClick);
cancelAnimationFrame(frame);
lenis.destroy();
};
}, [reduced]);
return null;
}