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
npm install framer-motion @motionone/utils
1'use client';2import { useRef, useEffect } from 'react';3import {4motion,5useScroll,6useSpring,7useTransform,8useVelocity,9useAnimationFrame,10useMotionValue,11} from 'framer-motion';12import { wrap } from '@motionone/utils';13import { cn } from '@/lib/utils';1415interface ParallaxProps {16children: string;17baseVelocity: number;18clasname?: string;19scrollDependent?: boolean; // Toggle scroll-dependent behavior20delay?: number; // Delay before animation starts21}2223export default function ScrollBaseAnimation({24children,25baseVelocity = -5,26clasname,27scrollDependent = false, // Default to false28delay = 0, // Default delay is 0 (no delay)29}: ParallaxProps) {30const baseX = useMotionValue(0);31const { scrollY } = useScroll();32const scrollVelocity = useVelocity(scrollY);33const smoothVelocity = useSpring(scrollVelocity, {34damping: 50,35stiffness: 400,36});37const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 2], {38clamp: false,39});4041const x = useTransform(baseX, (v) => `${wrap(-20, -45, v)}%`);4243const directionFactor = useRef<number>(1);44const hasStarted = useRef(false); // Track animation start status4546useEffect(() => {47const timer = setTimeout(() => {48hasStarted.current = true; // Start animation after the delay49}, delay);5051return () => clearTimeout(timer); // Cleanup on unmount52}, [delay]);5354useAnimationFrame((t, delta) => {55if (!hasStarted.current) return; // Skip if delay hasn't passed5657let moveBy = directionFactor.current * baseVelocity * (delta / 1000);5859// Reverse direction if scrollDependent is true60if (scrollDependent) {61if (velocityFactor.get() < 0) {62directionFactor.current = -1;63} else if (velocityFactor.get() > 0) {64directionFactor.current = 1;65}66}6768moveBy += directionFactor.current * moveBy * velocityFactor.get();6970baseX.set(baseX.get() + moveBy);71});7273return (74<div className='overflow-hidden whitespace-nowrap flex flex-nowrap'>75<motion.div76className='flex whitespace-nowrap gap-10 flex-nowrap'77style={{ x }}78>79<span className={cn(`block text-[8vw]`, clasname)}>{children}</span>80<span className={cn(`block text-[8vw]`, clasname)}>{children}</span>81<span className={cn(`block text-[8vw]`, clasname)}>{children}</span>82<span className={cn(`block text-[8vw]`, clasname)}>{children}</span>83</motion.div>84</div>85);86}
Prop | Type | Default | Description |
---|---|---|---|
children | string | The content to be animated with a parallax effect. | |
baseVelocity | number | -5 | The base velocity for the parallax effect. |
clasname | string | Optional CSS class for styling the component. | |
scrollDependent | boolean | false | Whether the animation should depend on scroll. |
delay | number | 0 | Delay before the animation starts (in milliseconds). |