Docs
Ticker
A ticker component that animates number on change
Installation
Run the following command
It will create a new file called ticker.tsx
inside the components/animata/text
directory.
mkdir -p components/animata/text && touch components/animata/text/ticker.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import { useCallback, useEffect, useRef } from "react";
import { motion, useMotionValue, useSpring } from "framer-motion";
import { cn } from "@/lib/utils";
function Number({
value,
index,
total,
delay,
className,
getHeight,
}: {
value: string;
index: number;
getHeight: () => number;
className?: string;
total: number;
delay?: number;
}) {
const numberRef = useRef<HTMLDivElement>(null);
const motionValue = useMotionValue(0);
const springValue = useSpring(motionValue, {
stiffness: 150 - index * 2,
damping: 15,
});
const isRaw = String(+value) !== value;
useEffect(() => {
if (isRaw || !numberRef.current) {
return;
}
const update = () => {
const height = getHeight();
springValue.set(-height * +value);
// Add a delay to prevent the spring from firing too early.
};
if (!delay) {
update();
return;
}
const timer = setTimeout(update, (total - index) * Math.floor(Math.random() * delay));
return () => clearTimeout(timer);
}, [value, isRaw, springValue, getHeight, index, total, delay]);
if (isRaw) {
return <span>{value}</span>;
}
return (
<motion.div
ref={numberRef}
style={{
translateY: springValue,
}}
>
{Array.from({ length: 10 }).map((_, i) => (
<motion.div className={className} key={i}>
{i}
</motion.div>
))}
</motion.div>
);
}
export default function Ticker({
value,
delay,
className,
numberClassName,
}: {
value: string;
className?: string;
numberClassName?: string;
delay?: number;
}) {
const parts = String(value).trim().split("");
const divRef = useRef<HTMLDivElement>(null);
const getHeight = useCallback(() => divRef.current?.getBoundingClientRect().height ?? 0, []);
return (
<div
className={cn(
"relative overflow-hidden whitespace-pre tabular-nums text-foreground",
className,
)}
>
<div className="absolute inset-0 flex min-w-fit">
{parts.map((part, index) => (
<Number
getHeight={getHeight}
index={index}
key={index}
value={part}
total={parts.length}
className={numberClassName}
delay={delay}
/>
))}
</div>
<div ref={divRef} className="invisible min-w-fit">
{value}
</div>
</div>
);
}
Credits
Built by hari