diff --git a/app/components/SmoothScroll.tsx b/app/components/SmoothScroll.tsx new file mode 100644 index 0000000..8a5cc45 --- /dev/null +++ b/app/components/SmoothScroll.tsx @@ -0,0 +1,80 @@ +import type { JSX } from "solid-js"; +import { createSignal, onCleanup } from "solid-js"; + +type SmoothScrollProps = JSX.IntrinsicElements["div"] & { + children: JSX.Element; +}; + +export function SmoothScroll(props: SmoothScrollProps) { + const [isScrolling, setIsScrolling] = createSignal(false); + let animationFrameId: number | null = null; + + const easeOutQuad = (t: number, b: number, c: number, d: number) => { + const time = t / d; + return -c * time * (time - 2) + b; + }; + + const smoothScroll = (deltaY: number) => { + const scrollSpeed = 3; + const currentScroll = window.scrollY; + const targetScroll = deltaY * scrollSpeed; + const duration = 300; + const startTime = performance.now(); + + const animateScroll = (currentTime: number) => { + const elapsedTime = currentTime - startTime; + const ease = easeOutQuad( + elapsedTime, + currentScroll, + targetScroll, + duration, + ); + + window.scrollTo(0, ease); + + if (elapsedTime < duration) { + animationFrameId = requestAnimationFrame(animateScroll); + } else { + setIsScrolling(false); + } + }; + + animationFrameId = requestAnimationFrame(animateScroll); + }; + + const isMobile = () => { + const regex = + /Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; + return regex.test(navigator.userAgent); + }; + + const handleWheel = (event: WheelEvent) => { + if (isMobile()) return; + + event.preventDefault(); + if (isScrolling()) { + if (animationFrameId !== null) { + cancelAnimationFrame(animationFrameId); + } + } + + requestAnimationFrame(() => { + smoothScroll(event.deltaY); + setIsScrolling(false); + }); + + setIsScrolling(true); + }; + + onCleanup(() => { + if (animationFrameId !== null) { + cancelAnimationFrame(animationFrameId); + } + }); + + return ( +