An animated number counter in HTML (the count-up done well)
A count-up counter is the smallest data-viz pattern. Done well, it sells a number; done badly, it dances. Here's the version worth shipping.
A count-up counter is the smallest possible data visualization. Number goes from 0 to a target value over a fixed duration. Two thirds of the implementations on Stack Overflow get it wrong in the same way: linear interpolation, no tabular numerals, the digits dance, the layout reflows. The result reads as broken.
Here is the version that doesn't.
The four ingredients
- An ease-out curve. Linear count-ups feel mechanical. Ease-out (
1 - (1 - t)³) lands the number on its target with finesse. font-variant-numeric: tabular-nums. Every digit takes the same width. The number "248" and "100" occupy the same horizontal space.- A fixed integer formatter.
Math.round(target * u).toLocaleString()produces "248,400" with commas. Without the formatter, you get "248400" and the eye has to count digits. - The right duration. 1.2-1.8 seconds for marketing animations. Faster reads as a flicker; slower reads as a stunt.
The 30 lines
<style>
.n {
font-size: 320px; font-weight: 900; letter-spacing: -.05em;
font-variant-numeric: tabular-nums; line-height: 1; color: #ff3b1f;
}
</style>
<div class="n" id="n">0</div>
<script>
const TARGET = 124000;
const DURATION = 1.4;
const el = document.getElementById("n");
const ease = (u) => 1 - Math.pow(1 - Math.max(0, Math.min(1, u)), 3);
function render(t) {
const u = ease(t / DURATION);
el.textContent = Math.round(TARGET * u).toLocaleString();
}
window.addEventListener("hf-seek", (e) => render(e.detail.time));
render(0);
</script>Thirty lines including the styles. The render(t) contract is what makes it deterministic-renderable — the same t always produces the same DOM, so the MP4 export is exact.
The pitfalls
requestAnimationFrame ties the count-up to wall time. It works in the browser; it breaks the moment you try to render to MP4. Always write the count-up as a function of t, not a side effect of a frame loop.
Floating-point drift. Math.round(TARGET * u) is the formatter. Without Math.round, the digits flicker between 124,000.0 and 124,000.1 on the last frames.
Prefix and suffix. A $ prefix or a % suffix should be outside the animated span — otherwise it counts up too. The prefix is constant; only the number animates.
Where this pattern lives
It's the count-up that powers KPI cards, year-in-review hero numbers, IPO announcements, mortgage-calculator "you saved $X" cards, fitness app "steps today" widgets. The same 30 lines, with the right typography and easing, carries all of them.
For the KPI-card flavor specifically, see animated KPI cards that look like money. For longer-form motion graphics built from primitives like this, see motion graphics in 80 lines.
Rendering to MP4
Once it lives as render(t), getting an MP4 out of it is a one-step process — open it in the playground, pick dimensions, render. The output is a deterministic MP4 at any size you need (1080p hero, 1200×630 OG, 1080×1920 vertical).
A count-up at 1200×630 is the unit asset for an animated launch announcement. A count-up at 1080×1920 is a TikTok of a stat. Same source, different export.
A working summary
A count-up is thirty lines if you respect the four ingredients: ease-out, tabular-nums, integer formatter, right duration. It's a thousand lines and an After Effects file if you don't. Pick the first one.
Cite this postBibTeX · APA · Markdown
@misc{okafor2026animated,
author = {Marcus Okafor},
title = {An animated number counter in HTML (the count-up done well)},
year = {2026},
url = {https://hyperframes.video/blog/animated-number-counter-html},
note = {HyperFrames blog}
}Marcus Okafor. (2026, May 19). An animated number counter in HTML (the count-up done well). HyperFrames. https://hyperframes.video/blog/animated-number-counter-html
[An animated number counter in HTML (the count-up done well)](https://hyperframes.video/blog/animated-number-counter-html) — Marcus Okafor, 2026
Marcus leads design and motion at HyperFrames. Before that he shipped editorial motion for newsrooms and product launches. He thinks every easing curve has a personality.
How to animate your logo (without After Effects)
A logo reveal in pure CSS — spring overshoot, wordmark stagger, and a render straight to MP4. No timeline tool, no plugins.
Scroll-driven video: turning timelines into scroll positions
CSS scroll-driven animations finally went baseline in 2026. A practical tutorial on mapping video timelines to scroll positions, when to use scroll vs video, and the hybrid pattern we use on hyperframes.dev.
Motion graphics in 80 lines
A complete title sequence — bouncy text, parallax backdrop, signal-color accent, cinematic ease — written in 80 lines of plain HTML. No framework. No tooling beyond the browser.
Building with HyperFrames? Come hang out.
We're on GitHub, in Discord, and the playground is one click away. Bring weird ideas — we collect them.