106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
"use client"
|
|
|
|
import 'lenis/dist/lenis.css'
|
|
import { ReactLenis } from "lenis/react"
|
|
import type { LenisRef } from "lenis/react"
|
|
import React, { useEffect, useMemo, useRef } from "react"
|
|
|
|
/**
|
|
* LenisRoot mounts a Lenis instance at the document root.
|
|
* - Runs only on the client ("use client")
|
|
* - Uses reasonable defaults for desktop wheel/touch
|
|
* - Respects prefers-reduced-motion by disabling smoothing
|
|
*
|
|
* Notes:
|
|
* - The ReactLenis component exposes a ref (LenisRef) when you need to call methods
|
|
* such as scrollTo or raf yourself. We attach a ref here so the instance can be
|
|
* inspected during development and is ready for any future integrations.
|
|
* - Importing 'lenis/dist/lenis.css' ensures the optional CSS required by Lenis is loaded.
|
|
*/
|
|
export function LenisRoot() {
|
|
const lenisRef = useRef<LenisRef | null>(null)
|
|
|
|
const prefersReducedMotion =
|
|
typeof window !== "undefined" &&
|
|
typeof window.matchMedia === "function" &&
|
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
|
|
const options = useMemo(
|
|
() => ({
|
|
// Let Lenis drive its own RAF loop (recommended for ReactLenis)
|
|
autoRaf: true,
|
|
// Enable smooth wheel scrolling when the user hasn't requested reduced motion
|
|
smoothWheel: !prefersReducedMotion,
|
|
// Enable a gentle smoothing for touch devices (set true to get smoothing on mobile)
|
|
// You can change this to `false` if you prefer native touch scrolling.
|
|
smoothTouch: !prefersReducedMotion,
|
|
// Enable anchor handling so links to #ids are handled by Lenis
|
|
anchors: true as const,
|
|
// Interpolation factor (0..1) - lower = more smoothing. When reduced motion is requested, disable smoothing.
|
|
lerp: prefersReducedMotion ? 1 : 0.08,
|
|
}),
|
|
[prefersReducedMotion]
|
|
)
|
|
|
|
useEffect(() => {
|
|
// Poll a few times until the ReactLenis ref is populated, then expose the instance
|
|
let checks = 0
|
|
const maxChecks = 20
|
|
const interval = setInterval(() => {
|
|
checks += 1
|
|
|
|
if (lenisRef.current?.lenis) {
|
|
// eslint-disable-next-line no-console
|
|
console.debug("Lenis initialized", lenisRef.current.lenis)
|
|
// Expose on window for quick debugging in devtools
|
|
try {
|
|
// @ts-expect-error - dev-only debugging helper
|
|
window.__lenis = lenisRef.current.lenis
|
|
} catch (e) {
|
|
/* ignore */
|
|
}
|
|
|
|
// Ensure the instance is running
|
|
try {
|
|
lenisRef.current.lenis.start?.()
|
|
} catch (e) {
|
|
/* ignore */
|
|
}
|
|
|
|
clearInterval(interval)
|
|
return
|
|
}
|
|
|
|
if (checks >= maxChecks) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn("Lenis did not initialize within expected time")
|
|
clearInterval(interval)
|
|
}
|
|
}, 100)
|
|
|
|
return () => {
|
|
clearInterval(interval)
|
|
}
|
|
}, [])
|
|
|
|
React.useEffect(() => {
|
|
const instance = lenisRef.current?.lenis
|
|
if (!instance) return
|
|
|
|
const onScroll = (e: any) => {
|
|
// eslint-disable-next-line no-console
|
|
console.debug("lenis:scroll", e)
|
|
}
|
|
|
|
instance.on?.("scroll", onScroll)
|
|
|
|
return () => {
|
|
instance.off?.("scroll", onScroll)
|
|
}
|
|
}, [])
|
|
|
|
return <ReactLenis root options={options} ref={lenisRef} />
|
|
}
|
|
|
|
export default LenisRoot
|