Command Palette

Search for a command to run...

Text Marquee

A text marquee animation with options for unidirectional and bidirectional scrolling, which changes direction based on the scroll position

Star the repo if you like itStar the repo if you like itStar the repo if you like itStar the repo if you like it
Share it if you like itShare it if you like itShare it if you like itShare it if you like it

Installation

Install the following dependencies:

pnpm add motion

Create a file at components/motion/text-marquee.tsx and paste the given code.

"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>
    );
}

Update the import paths to match your project setup.