Advertisement

Odometer Number Counter

| by Vladimir | 2 min read | code by Jhey
Intermediate

Tech & Dependencies

HTML CSS Babel
React ReactDOM dat.gui

Features

  • Odometer Effect
  • CSS Custom Easing
  • 3D Explode View
  • dat.gui Integration

Browser Support

Chrome 90+ Edge 90+ Firefox 88+ Safari 15+

Core

This is an Odometer Number Counter. It visualizes numerical changes (such as MRR, follower counts, or revenue) using a mechanical rolling effect. Its function is to provide a highly configurable, tactile alternative to instant number swapping, bringing physical momentum to digital dashboards.

Specs

  • Weight: ~130 KB (including React and dat.gui dependencies via CDN).
  • Performance: Excellent. The heavy lifting of the rolling animation is offloaded to the browser’s CSS compositor using translate and transition. React only handles calculating the required translation index.
  • Theming / Customization: Highly customizable. A built-in dat.gui panel allows real-time adjustment of Min/Max values, step increments, currency types, zero-padding, transition duration, and complex easing curves.
  • Responsiveness: Fluid. Uses clamp() for font sizes, scaling seamlessly across viewport widths.
  • Graceful Degradation: The visual counter relies on CSS transitions. If CSS fails, the raw numbers inside the visually-hidden .sr-only span ensure the data remains accessible.

Anatomy

The architecture separates state logic from the visual rendering pipeline.

  • React (The Skeleton & Nervous System): The <App> component manages the global state (value, min, max, ease). It passes this state to the <Counter>, which formats the number using Intl.NumberFormat and breaks it down into individual <Character> components.
  • HTML (The DOM): Each <Character> is a window (mask: linear-gradient(...)) showing a slice of a taller .character__track. This track contains a hardcoded column of numbers: 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0.
  • CSS (The Skin): A highly complex set of linear easing functions (--back, --bounce, --elastic). The “roll” is triggered by changing the --v (value) inline CSS variable on the track.

Logic

The standout mechanism is the Infinite Rolling Illusion using offset indices.

.character__track {
	translate: 0 calc((var(--v) + 1) * (var(--line-height) * -1));
	transition: translate calc(var(--transition) * 1s) var(--ease);
}

The track contains 9 at the top, then 0-9, and another 0 at the bottom. React passes the target number (e.g., 4) as the --v variable. The CSS then translates the track upward by (4 + 1) = 5 line heights. The + 1 offset accounts for the initial 9 at the top of the column. This setup allows the track to handle single-digit increments or massive jumps smoothly, using the CSS transition engine to handle the in-between frames.

Feel

Mechanical and satisfying. Depending on the easing selected via the control panel (e.g., elastic, bounce, power), the numbers snap into place with physical weight. The inclusion of the “Explode” toggle rotates the entire component into 3D space, separating the front mask from the background track, providing a beautiful “behind-the-scenes” look at how the mechanical illusion is achieved.

Advertisement