Files
sass-mohfarawati/components/scroll-smoother-provider.tsx
MOH 1a7b3397f6
CI / quality (push) Waiting to run
last update
2026-07-14 20:10:38 +02:00

76 lines
2.3 KiB
TypeScript

"use client";
import { useEffect, useRef } from "react";
import { usePathname } from "next/navigation";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollSmoother } from "gsap/ScrollSmoother";
gsap.registerPlugin(ScrollTrigger, ScrollSmoother);
const MOBILE_BREAKPOINT = 768;
export function ScrollSmootherProvider() {
const smootherRef = useRef<ScrollSmoother | null>(null);
const pathname = usePathname();
const isFirstRenderRef = useRef(true);
useEffect(() => {
const prefersReduced = window.matchMedia(
"(prefers-reduced-motion: reduce)",
).matches;
const isMobile = window.innerWidth < MOBILE_BREAKPOINT;
if (prefersReduced) return;
const smoother = ScrollSmoother.create({
wrapper: "#smooth-wrapper",
content: "#smooth-content",
smooth: isMobile ? 0.6 : 1.2,
effects: !isMobile,
normalizeScroll: !isMobile,
ignoreMobileResize: true,
});
smootherRef.current = smoother;
return () => {
smoother.kill();
smootherRef.current = null;
};
}, []);
// The layout that mounts this provider persists across client-side route
// changes, so the ScrollSmoother instance above is created once and never
// recreated when navigating between pages. Its internal scroll position
// and measured content height stay tied to whatever page was on screen
// before the navigation. If the next page is a different height, the
// stale height/position pairing snaps into place the instant the new page
// paints — most visible as the fixed header's active-nav-item background
// jumping instead of easing, since it briefly renders against the old,
// now-incorrect scroll transform. Resetting scroll on every pathname
// change and refreshing ScrollTrigger's measurements once the new page has
// painted keeps the smoother in sync with what's actually on screen.
useEffect(() => {
if (isFirstRenderRef.current) {
isFirstRenderRef.current = false;
return;
}
const smoother = smootherRef.current;
if (!smoother) {
return;
}
smoother.scrollTo(0, false);
const refreshFrame = requestAnimationFrame(() => {
ScrollTrigger.refresh();
});
return () => cancelAnimationFrame(refreshFrame);
}, [pathname]);
return null;
}