webdevpuneet.com

Odometer Rolling Stat Counters — Free odometer.js Dashboard Snippet

Odometer Rolling Stat Counters · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

<section class="ost-wrap">
  <header class="ost-head"><h1>Quarter in Numbers</h1><p>Numbers roll like a mechanical odometer instead of just changing instantly — scroll into view or click the button below.</p></header>
  <div class="ost-grid">
    <div class="ost-tile"><div class="odometer" id="ostRevenue">0</div><span class="ost-label">Revenue ($k)</span></div>
    <div class="ost-tile"><div class="odometer" id="ostUsers">0</div><span class="ost-label">Active Users</span></div>
    <div class="ost-tile"><div class="odometer" id="ostDeploys">0</div><span class="ost-label">Deploys</span></div>
    <div class="ost-tile"><div class="odometer" id="ostUptime">0</div><span class="ost-label">Uptime %</span></div>
  </div>
  <button class="ost-btn" id="ostReplay">Roll again ↻</button>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), 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

Independent digit reels
Each digit animates on its own vertical strip, not one linear count.
Update-driven API
.update(value) both sets and animates to the new number.
Scroll-triggered roll
An IntersectionObserver fires the roll once tiles are visible.
Replay button
Resets to 0 and rolls back in on demand.
Configurable duration
Odometer.options.duration controls roll speed globally.
Swappable themes
Six built-in CSS themes ship with the library.
Dashboard-ready tiles
A four-stat grid layout suited to admin panels.
One observer, four odometers
A single scroll trigger drives every tile together.

About this UI Snippet

Odometer Rolling Stat Counters — Mechanical Digit Rolls with odometer.js

Screenshot of the Odometer Rolling Stat Counters snippet rendered live

Odometer.js recreates the physical odometer effect — each digit spins independently on its own vertical reel until it lands on the new value — for arbitrary numbers on a web page. This snippet uses it to build a row of dashboard stat tiles that roll from zero to their real value the moment the grid scrolls into view, with a replay button to trigger the animation again on demand.

Odometer's API is the value itself

Unlike a typical animation library where you call something like .play() or .animate(), odometer.js works by intercepting how you update the element's value: once an element is wrapped with new Odometer({ el, value: 0 }), calling .update(newValue) — or, in odometer's classic usage, simply assigning to the underlying element's innerHTML — triggers the roll. The library replaces the element's content with a structure of individually-positioned digit reels, so setting a new value doesn't just swap text, it animates each digit to its new position on its own reel.

Triggering the roll on scroll

Rather than rolling on page load (which the visitor might miss if the stats are below the fold), this snippet wraps the four tiles in a single IntersectionObserver watching the .ost-grid container. Once it's roughly 40% visible, rollIn() fires once for all four odometers and the observer disconnects — this is the same "reveal once, on scroll" pattern used by Count Up and Number Ticker, just with odometer.js handling the actual digit animation instead of a manual requestAnimationFrame tween.

Independent digit reels

Because each digit rolls on its own reel rather than the whole number counting up as one unit, larger numbers (like the 15,230 active-users stat) look visually distinct from a typical linear count-up — the ones digit might cycle through several values while the ten-thousands digit only moves once. This is odometer.js's signature visual character and the reason to reach for it specifically over a generic number-tweening approach.

Theme CSS handles the visuals

The CDN's odometer-theme-minimal.css supplies the actual sliding/rolling visual mechanics (each digit as a small vertically-scrolling strip) — this snippet's own CSS only sets color, size, and weight so the digits match the surrounding dashboard palette. Odometer ships several built-in themes (minimal, car, digital, plaza, slot machine, train station) that can be swapped by simply linking a different theme CSS file.

Customizing it

Change Odometer.options.duration for a faster or slower roll, swap the theme stylesheet for a different visual style, or feed live values from an API instead of hardcoded targets. Pair this with Dashboard Widget Grid or Stats Card for a full metrics panel.

Step by step

How to Use

  1. 1
    Add the Odometer CDN CSS and JSInclude the odometer-theme-minimal.css and odometer.min.js from the CDN panel.
  2. 2
    Paste HTML, CSS, and JSFour stat tiles and a replay button render, starting at 0.
  3. 3
    Scroll the tiles into viewEach digit rolls independently up to its target value.
  4. 4
    Click Roll againValues reset to 0, then roll back in after a short delay.
  5. 5
    Change the target valuesEdit the targets object in the JS panel with your real numbers.
  6. 6
    Swap the themeLink a different odometer theme CSS file for a new visual style.

Real-world uses

Common Use Cases

Admin panel KPI counters
Pair with dashboard widget grid panels, rolling each digit on its own reel when the numbers update.
SaaS metrics pages
Roll in usage stats alongside a stats card, triggered once by an IntersectionObserver when the tiles scroll into view.
Investor and pitch pages
Give key figures a memorable, mechanical entrance on a web pitch deck, and use the replay button to demo them on demand.
Pitch deck headline figures
Offer a more physical alternative to count up, where each digit spins independently on its own vertical strip.
Live event and annual report pages
Roll updated ticket or attendee counts during an event, or reveal yearly figures with .update(value), which both sets and animates to the new number.
Related: Restaurant Order Status Tracker
See the Restaurant Order Status Tracker for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Odometer wraps the target element and, on construction, replaces its content with a set of digit-reel elements. Calling .update(newValue) tells Odometer to compute the difference between the current and new digits and animate each reel to its new resting position — the animation is inherent to how the value change is applied, not a separate method you call afterward.

Rolling immediately on load risks the animation finishing before a visitor scrolls the stats into view, especially if the tiles sit below the fold. Watching the grid with an IntersectionObserver and rolling once it's roughly 40% visible ensures the visitor actually sees the digits animate, which is the entire point of using odometer.js instead of just rendering the final numbers.

Yes — call odometers[id].update(newValue) any time you receive fresh data, for example from a polling interval or a websocket message. Each call re-triggers the roll from the current displayed value to the new one, which is exactly the same mechanism used for the initial scroll-triggered roll-in.

Odometer ships several themes as separate CSS files (minimal, car, digital, plaza, slot machine, train station) — swap the odometer-theme-minimal.css CDN link for a different theme file's URL and the digit rendering changes without any JavaScript or HTML changes, since the theme purely controls the reel's visual presentation.

Install the odometer.js npm package (or keep the CDN scripts), and construct new Odometer({ el, value }) inside a mount effect (useEffect, onMounted, or ngAfterViewInit) targeting a ref to the digit container element. Call .update() whenever your component's underlying stat data changes, and keep the IntersectionObserver setup in the same effect with a matching cleanup that disconnects it on unmount.