"use client"; import { motion, useAnimationFrame, useMotionValue, useScroll, useSpring, useTransform, } from "motion/react"; import { useEffect, useRef, useState } from "react"; interface ScrollVelocityTextProps { text: string; baseVelocity?: number; className?: string; } function wrap(min: number, max: number, v: number) { const rangeSize = max - min; return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min; } export default function ScrollVelocityText({ text, baseVelocity = 2, className = "", }: ScrollVelocityTextProps) { const baseX = useMotionValue(0); const { scrollY } = useScroll(); const scrollVelocity = useMotionValue(0); const smoothVelocity = useSpring(scrollVelocity, { damping: 50, stiffness: 300, }); const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], { clamp: false, }); const [repetitions, setRepetitions] = useState(4); const containerRef = useRef(null); useEffect(() => { const handleResize = () => { setRepetitions(Math.ceil(window.innerWidth / 400) + 2); }; handleResize(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); // Track scroll velocity const prevScrollY = useRef(0); useEffect(() => { return scrollY.on("change", (latest) => { const delta = latest - prevScrollY.current; prevScrollY.current = latest; scrollVelocity.set(Math.abs(delta)); }); }, [scrollY, scrollVelocity]); const directionFactor = useRef(1); const x = useTransform(baseX, (v) => `${wrap(-25, 0, v)}%`); useAnimationFrame((_, delta) => { let moveBy = directionFactor.current * baseVelocity * (delta / 1000); const vf = velocityFactor.get(); if (vf < 0) { directionFactor.current = -1; } else if (vf > 0) { directionFactor.current = 1; } moveBy += directionFactor.current * moveBy * vf; baseX.set(baseX.get() + moveBy); }); return (
{[...Array(repetitions)].map((_, i) => ( {text} • ))}
); }