webdevpuneet.com

Number Ticker — Free HTML CSS JS Animated Counter Snippet

Number Ticker · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations

Copy & paste

Source Code

<section class="nt-stats" id="ntStats">
  <div class="nt-stat"><div class="nt-num" data-to="48295" data-prefix="$"></div><div class="nt-label">Revenue this month</div></div>
  <div class="nt-stat"><div class="nt-num" data-to="1240000" data-suffix="+"></div><div class="nt-label">API requests / day</div></div>
  <div class="nt-stat"><div class="nt-num" data-to="99.98" data-decimals="2" data-suffix="%"></div><div class="nt-label">Uptime</div></div>
  <button type="button" class="nt-replay" id="ntReplay">↻ Replay</button>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Data-attribute targets
to, decimals, prefix, and suffix per stat.
Odometer easing
easeOutExpo spins down and settles.
Thousands separators
Formatted on the integer part each frame.
Decimal support
Fixed places preserved throughout.
Tabular figures
Equal-width digits prevent jitter.
Scroll-triggered
IntersectionObserver fires once in view.
Replayable
Re-run all tickers on demand.
Independent stats
Each animates on its own.

About this UI Snippet

Number Ticker — Stats That Count Up With an Odometer Ease

Screenshot of the Number Ticker snippet rendered live

The number ticker is the animated stat counter that rolls a value up from zero to its target with a satisfying spin-down — the figures you see in "trusted by" and metrics sections. This snippet builds it with plain HTML, CSS, and vanilla JavaScript, with proper formatting (thousands separators, decimals, prefixes and suffixes) and a scroll trigger.

Data-driven targets

Each number declares its target and formatting through data attributes: data-to for the value, optional data-decimals, data-prefix (like $), and data-suffix (like % or +). The script reads these, so the same animation handles money, percentages, and large counts without per-stat code. This keeps the markup declarative — you describe the number, the script animates it.

The ease-out spin-down

The animation runs on requestAnimationFrame with delta timing against a fixed duration. The progress is passed through an easeOutExpo curve (1 - 2^(-10p)), which starts fast and decelerates sharply as it approaches the target — exactly the feel of an odometer or counter spinning down and settling. A linear count-up looks mechanical; this easing is what gives the ticker its momentum and a clean stop. The displayed value each frame is target * eased, formatted and written to the element.

Correct number formatting

Raw counting produces ugly intermediate values, so a format() helper rounds (or fixes decimals) and inserts thousands separators into the integer part with a regex, leaving any decimal part alone. So 1240000 animates through 1,240,000 rather than a bare digit string, and 99.98 keeps its two decimals throughout. The numbers also use font-variant-numeric: tabular-nums so every digit has the same width — without it, the figure would jitter horizontally as digits change, which looks broken on a counting number.

Scroll-triggered

Counting before the stats are visible wastes the effect, so an IntersectionObserver starts all tickers when the section is 30% in view, then disconnects so it fires once. This is the standard, performant trigger for on-scroll animations — no scroll listener, no per-frame visibility checks. A replay button re-runs them for demos.

Why rAF over a CSS counter

CSS has no real way to animate a formatted number with separators and easing, so JavaScript drives it — but only with lightweight per-frame text updates, not layout changes. The gradient text fill and the tabular figures are CSS; the script only writes the formatted string each frame, which is cheap even for several stats at once.

Customizing it

Change the duration for a faster or slower roll, swap the easing (e.g. a gentler easeOutQuart), adjust formatting (currency symbols, locale separators via toLocaleString), or restyle the stat cards. Add as many stats as you like — each is independent. Pair it with a metric card grid or a logo marquee for a complete trust section.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA row of stat cards renders with target values.
  2. 2
    Scroll them into viewEach number counts up with an ease-out spin-down.
  3. 3
    Note the formattingThousands separators, decimals, and $ / % / + show correctly.
  4. 4
    Click ReplayThe tickers re-run from zero.
  5. 5
    Add a statCopy a card and set its data-to and formatting.
  6. 6
    Tune the rollChange the duration and easing.

Real-world uses

Common Use Cases

Stat sections
Pair with a metric card grid, where data attributes set each stat's target, decimals, prefix and suffix.
Trust and traction strips
Follow a logo marquee of customers with the numbers that prove their scale, rolling up on scroll.
Dashboard KPI reveals
Animate figures on a status dashboard with an easeOutExpo spin-down that settles satisfyingly on the final value.
Landing page metrics
Headline key numbers above a testimonial wall, with thousands separators applied on every animation frame as the count rises.
Savings on pricing pages
Show how much customers save beside a pricing card, with fixed decimal places preserved throughout the count.

Got questions?

Frequently Asked Questions

The progress is passed through an easeOutExpo curve, 1 minus 2 to the power of -10p, which starts fast and decelerates sharply as it nears the target — the feel of an odometer settling. The displayed value each frame is target times the eased progress. A linear count-up looks mechanical; this easing gives the momentum and clean stop.

A format helper rounds or fixes decimals, then inserts thousands separators into the integer part with a regex, leaving any decimal part alone. So a value animates through 1,240,000 rather than a bare digit string, and a percentage keeps its decimals. Prefixes and suffixes from data attributes are added around the formatted number.

The numbers use font-variant-numeric: tabular-nums, which gives every digit the same width. Without it, proportional digits change width as they count, so the figure would shift left and right each frame, which looks broken. Tabular figures keep the number visually stable while it rolls.

An IntersectionObserver starts all tickers when the section is 30% in view, then disconnects so they animate once. This avoids counting before the stats are visible, needs no scroll listener or per-frame visibility checks, and is the standard performant trigger for on-scroll animations. A replay button re-runs them.

Make a Ticker component that takes the target and formatting as props and runs the rAF loop in a mount effect (triggered by an IntersectionObserver), writing the formatted value to a ref'd element rather than state to avoid re-rendering every frame. Cancel the frame on unmount. The gradient and tabular CSS port directly; in Tailwind use tabular-nums and bg-clip-text.