NEW
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
Learn more
Skip to content
Docs

Ticker

A ticker component that animates number on change

Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/text/ticker.json

Manual

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 { motion, useInView, useMotionValue, useSpring } from "motion/react";
import { useCallback, useEffect, useRef } from "react";
 
import { cn } from "@/lib/utils";
 
function Number({
  value,
  index,
  total,
  delay,
  className,
  getHeight,
  isInView,
}: {
  value: string;
  index: number;
  getHeight: () => number;
  className?: string;
  total: number;
  delay?: number;
  isInView: boolean;
}) {
  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 (!isInView || 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, isInView, 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 isInView = useInView(divRef, { once: true });
  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}
            isInView={isInView}
          />
        ))}
      </div>
      <div ref={divRef} className="invisible min-w-fit">
        {value}
      </div>
    </div>
  );
}

Credits

Built by hari