Text Effects
VelocityText
A looping text band that speeds up and leans when the page scrolls, and turns round when the scroll does.
Download
- React
- Tailwind CSS v4
- motion
components/ui/velocity-text.tsx
"use client";
import { useRef } from "react";
import {
motion,
useAnimationFrame,
useMotionValue,
useReducedMotion,
useScroll,
useSpring,
useTransform,
useVelocity,
} from "motion/react";
type VelocityTextProps = {
/** Text to loop. Make it long enough to fill at least a third of the row. */
children: string;
className?: string;
/** Resting speed, in % of one copy per second. Negative runs the other way. */
baseVelocity?: number;
};
const wrap = (min: number, max: number, v: number) => {
const range = max - min;
return ((((v - min) % range) + range) % range) + min;
};
/* VelocityText: a looping text band that speeds up and leans when the page scrolls, and turns round when the scroll does. */
export function VelocityText({ children, className = "", baseVelocity = 4 }: VelocityTextProps) {
const reduce = useReducedMotion();
const baseX = useMotionValue(0);
const { scrollY } = useScroll();
const velocity = useSpring(useVelocity(scrollY), { damping: 50, stiffness: 400 });
const boost = useTransform(velocity, [0, 1000], [0, 5], { clamp: false });
const skewX = useTransform(velocity, [-1500, 0, 1500], [12, 0, -12]);
// Four copies in the track; moving by 25% = one copy, so the loop is seamless.
const x = useTransform(baseX, (v) => `${wrap(-25, 0, v)}%`);
const direction = useRef(1);
useAnimationFrame((_, delta) => {
if (reduce) return;
const b = boost.get();
if (b < 0) direction.current = -1;
else if (b > 0) direction.current = 1;
baseX.set(baseX.get() - direction.current * baseVelocity * (delta / 1000) * (1 + Math.abs(b)));
});
return (
<div className={`overflow-hidden whitespace-nowrap ${className}`}>
<span className="sr-only">{children}</span>
<motion.div aria-hidden className="flex w-max" style={reduce ? undefined : { x, skewX }}>
{[0, 1, 2, 3].map((i) => (
<span key={i} className="pr-[0.5em]">
{children}
</span>
))}
</motion.div>
</div>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/velocity-text.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/velocity-text-demo.tsx
import { VelocityText } from "@/components/ui/velocity-text"; export default function VelocityTextExample() { return ( <div className="flex h-full flex-col justify-center gap-4 bg-[#121212] py-6 text-[#f4f1ea]"> <VelocityText className="text-[clamp(36px,7vw,80px)] font-black uppercase tracking-tight">Scroll the page and watch me run</VelocityText> <VelocityText baseVelocity={-3} className="text-[clamp(36px,7vw,80px)] font-black uppercase tracking-tight text-[#a78bfa]"> Faster when you move, calm when you stop </VelocityText> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| children | string | — | Text to loop. Long enough to fill a third of the row. |
| baseVelocity | number | 4 | Resting speed, % of one copy per second. Negative runs the other way. |
| className | string | "" | Type styles for the text. |