Odometer Number Counter
See the Pen Odometer Number Counter.
Tech & Dependencies
Features
- ✓ Odometer Effect
- ✓ CSS Custom Easing
- ✓ 3D Explode View
- ✓ dat.gui Integration
Browser Support
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.guidependencies via CDN). - Performance: Excellent. The heavy lifting of the rolling animation is offloaded to the browser’s CSS compositor using
translateandtransition. React only handles calculating the required translation index. - Theming / Customization: Highly customizable. A built-in
dat.guipanel 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-onlyspan 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 usingIntl.NumberFormatand 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.


