Text Marquee
A text marquee animation with options for unidirectional and bidirectional scrolling, which changes direction based on the scroll position
A text marquee animation with options for unidirectional and bidirectional scrolling, which changes direction based on the scroll position
Follow these simple steps to add the Text Marquee component to your project:
Install Dependencies
pnpm add motion
Create a new file: components/motions/text-marquee.tsx
and copy the code below:
"use client";
import { useRef, useEffect } from "react";
import {
motion,
useScroll,
useSpring,
useTransform,
useVelocity,
useAnimationFrame,
useMotionValue,
} from "motion/react";
import { wrap } from "@motionone/utils";
import { cn } from "@/lib/utils";
interface ParallaxProps {
children: string;
baseVelocity: number;
clasname?: string;
scrollDependent?: boolean; // Toggle scroll-dependent behavior
delay?: number; // Delay before animation starts
}
export default function ScrollBaseAnimation({
children,
baseVelocity = -5,
clasname,
scrollDependent = false, // Default to false
delay = 0, // Default delay is 0 (no delay)
}: ParallaxProps) {
const baseX = useMotionValue(0);
const { scrollY } = useScroll();
const scrollVelocity = useVelocity(scrollY);
const smoothVelocity = useSpring(scrollVelocity, {
damping: 50,
stiffness: 400,
});
const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 2], {
clamp: false,
});
const x = useTransform(baseX, (v) => `${wrap(-20, -45, v)}%`);
const directionFactor = useRef<number>(1);
const hasStarted = useRef(false); // Track animation start status
useEffect(() => {
const timer = setTimeout(() => {
hasStarted.current = true; // Start animation after the delay
}, delay);
return () => clearTimeout(timer); // Cleanup on unmount
}, [delay]);
useAnimationFrame((t, delta) => {
if (!hasStarted.current) return; // Skip if delay hasn't passed
let moveBy = directionFactor.current * baseVelocity * (delta / 1000);
// Reverse direction if scrollDependent is true
if (scrollDependent) {
if (velocityFactor.get() < 0) {
directionFactor.current = -1;
} else if (velocityFactor.get() > 0) {
directionFactor.current = 1;
}
}
moveBy += directionFactor.current * moveBy * velocityFactor.get();
baseX.set(baseX.get() + moveBy);
});
return (
<div className="flex flex-nowrap overflow-hidden whitespace-nowrap">
<motion.div
className="flex flex-nowrap gap-10 whitespace-nowrap"
style={{ x }}
>
<span className={cn(`block text-[8vw]`, clasname)}>
{children}
</span>
<span className={cn(`block text-[8vw]`, clasname)}>
{children}
</span>
<span className={cn(`block text-[8vw]`, clasname)}>
{children}
</span>
<span className={cn(`block text-[8vw]`, clasname)}>
{children}
</span>
</motion.div>
</div>
);
}
Adjust the import paths in both files according to your project's structure.