Text Marquee

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

UniDirectional

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

Bidirectional

Best Component library For DeveloperBest Component library For DeveloperBest Component library For DeveloperBest Component library For Developer

Installation

npm install framer-motion @motionone/utils
text-marquee.tsx
1
'use client';
2
import { useRef, useEffect } from 'react';
3
import {
4
motion,
5
useScroll,
6
useSpring,
7
useTransform,
8
useVelocity,
9
useAnimationFrame,
10
useMotionValue,
11
} from 'framer-motion';
12
import { wrap } from '@motionone/utils';
13
import { cn } from '@/lib/utils';
14
15
interface ParallaxProps {
16
children: string;
17
baseVelocity: number;
18
clasname?: string;
19
scrollDependent?: boolean; // Toggle scroll-dependent behavior
20
delay?: number; // Delay before animation starts
21
}
22
23
export default function ScrollBaseAnimation({
24
children,
25
baseVelocity = -5,
26
clasname,
27
scrollDependent = false, // Default to false
28
delay = 0, // Default delay is 0 (no delay)
29
}: ParallaxProps) {
30
const baseX = useMotionValue(0);
31
const { scrollY } = useScroll();
32
const scrollVelocity = useVelocity(scrollY);
33
const smoothVelocity = useSpring(scrollVelocity, {
34
damping: 50,
35
stiffness: 400,
36
});
37
const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 2], {
38
clamp: false,
39
});
40
41
const x = useTransform(baseX, (v) => `${wrap(-20, -45, v)}%`);
42
43
const directionFactor = useRef<number>(1);
44
const hasStarted = useRef(false); // Track animation start status
45
46
useEffect(() => {
47
const timer = setTimeout(() => {
48
hasStarted.current = true; // Start animation after the delay
49
}, delay);
50
51
return () => clearTimeout(timer); // Cleanup on unmount
52
}, [delay]);
53
54
useAnimationFrame((t, delta) => {
55
if (!hasStarted.current) return; // Skip if delay hasn't passed
56
57
let moveBy = directionFactor.current * baseVelocity * (delta / 1000);
58
59
// Reverse direction if scrollDependent is true
60
if (scrollDependent) {
61
if (velocityFactor.get() < 0) {
62
directionFactor.current = -1;
63
} else if (velocityFactor.get() > 0) {
64
directionFactor.current = 1;
65
}
66
}
67
68
moveBy += directionFactor.current * moveBy * velocityFactor.get();
69
70
baseX.set(baseX.get() + moveBy);
71
});
72
73
return (
74
<div className='overflow-hidden whitespace-nowrap flex flex-nowrap'>
75
<motion.div
76
className='flex whitespace-nowrap gap-10 flex-nowrap'
77
style={{ 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
}

Props

PropTypeDefaultDescription
childrenstringThe content to be animated with a parallax effect.
baseVelocitynumber-5The base velocity for the parallax effect.
clasnamestringOptional CSS class for styling the component.
scrollDependentbooleanfalseWhether the animation should depend on scroll.
delaynumber0Delay before the animation starts (in milliseconds).