Hi! ð
Have you ever caught yourself staring at the rating change on Lichess after a hard meditation of chess puzzle ❓
Precisely, "hard meditation".
Anyway on their mobile app, that little number animation as your rating climbs from, say, 300 to 313 — or plummets from 308 to 299 — carries a strange sort of magic.
For you who didn't get my prior jest. On Lichess, the starting rating is 1500.
To hit either 300 or 308, we have to go on a legendary, historic, multi-day losing streak like an ultra plonker.
That is a disciplined, methodical, unyielding commitment to the wrong move.
In order to consistently, impeccably, and flawlessly play the absolute worst move every single time, we have to calculate the winning move first just to make sure we don't ACCIDENTALLY play it. It takes a true, visionary ultra plonker to systematically engineer the absolute worst outcome conceivable.
I hope that elaboration delivers its intention. To expound. Moving on.
The same thing happens when you log into a sleek SaaS (Software as a Service) dashboard — metrics tick upwards or downwards with an effortless, mechanical elegance.
Recently, while browsing through a few modern dashboard templates, I found myself wondering —
Hm. ðĪ Hm.
Naturally, my initial thought was to use setInterval. Run a timer every few milliseconds, add a bit to a count variable, update the DOM, and call it a day.
Except... it was a complete disaster. A comical one. I even bought a notebook to do the maths to redo my steps. My calculation was correct, but apparently modern browsers cap setInterval to a minimum threshold (around 4–10 ms). And under any sort of CPU load, the timer drifts wildly. Small number ranges jumped instantly, large number ranges lagged behind like a heavily loaded tractor. Well. All right.
So the solution is ditching timers entirely and tapping into requestAnimationFrame!
Unlike setInterval, requestAnimationFrame synchronises directly with the display's native refresh rate — whether 60Hz, 120Hz, or beyond. Instead of guessing how many milliseconds each step should take, I track the elapsed time against the target duration on every frame render. It makes animating numbers vastly smoother, more reliable, and automatically pauses if the user switches tabs, saving precious CPU cycles.
Demonstration
Hello, I'm a demonstration. ⬇️
⬆️ Try that!
Not just by clicking ▶️ RUN button, you can also start the animation by hitting Enter key if your cursor is focused on an input.
About the interface:
-
From (number)inputA starting number.
-
To (number)inputA final number.
-
Duration (ms)inputHow long to animate in milliseconds.
-
EASE-INcheckboxApplies ease-in cubic curve in the animation instead of linear motion.
Ease-in = slow at the beginning then accelerate.
Can be combined with
EASE-OUT. -
EASE-OUTcheckboxApplies ease-out cubic curve in the animation instead of linear motion.
Ease-out = decelerate at almost the end.
Can be combined with
EASE-IN. -
RUNbuttonTo run the animation.
-
CLEARbuttonTo stop the animation and reset the output element state.
-
OUTPUTThe visual output.
Snippet
I've made the function to be modular so we can all enjoy it.
With explanations (comments).
Without comments.
Usage
Related Function
For large numbers, to be readable, we can format them first.
Here's how we format the numbers.
So these lines for output:
Can be wrapped with the number formatter function.
Include the number formatter function (formatNumberWithRegex) inside the factory function createNumberAnimator, and you're good to go.
As such:
Cheers. ðš
