Want to make your campaign metrics, impact numbers, or achievements stand out? A count-up animation draws attention by animating numbers as they increase—ideal for stats like signatures collected, square meters of land protected, or funds raised.
This article shows how to implement a dynamic counter that starts animating when it scrolls into view, using pure JavaScript and a bit of custom CSS.
What This Setup Does
Displays a number that animates from 0 up to a target value.
Automatically starts when the user scrolls the number into view (using IntersectionObserver).
Includes thousands separators (with periods for European formatting).
Fully responsive, using vw units for scalable font sizes.
Step 1: Insert the Counter Element
Add an HTML block where you want the number to appear and paste:
<div id="counter">0 m²</div>
You can replace m² with any unit (e.g., €, %, supporters, etc.).
Step 2: Add the Styling
Paste the following CSS into a <style> block in the same HTML block (or globally):
<style>
#counter {
font-family: sans-serif;
color: white;
font-size: 5vw;
font-weight: bold;
margin-top: 2vw;
text-align: center;
}
</style>
Step 3: Add the Count-Up Logic
Below the HTML and CSS, paste this JavaScript:
<script>
var targetNumber = 60000; // Target number
var delay = 500; // Delay before counting (in ms)
var duration = 50; // Base duration per step
var startSpeed = Math.ceil(duration / 500);
var maxSpeed = Math.ceil(duration / 500);
var currentSpeed = startSpeed;
function countUp() {
var current = parseInt(document.getElementById('counter').innerHTML.replace(/\D/g, ''));
if (current < targetNumber) {
var step = current < 50000 ? 1000 : 10000;
document.getElementById('counter').innerHTML = numberWithCommas(current + step) + " m²";
if (currentSpeed < maxSpeed) {
currentSpeed *= 1.04;
}
setTimeout(() => requestAnimationFrame(countUp), currentSpeed);
}
}
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
function startCountUp() {
setTimeout(countUp, delay);
}
var observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
startCountUp();
observer.disconnect();
}
});
}, {
threshold: 0.5
});
observer.observe(document.getElementById('counter'));
</script>
Notes and Adjustments
Change the target value via targetNumber = 60000.
For international number formatting, you can adapt numberWithCommas() using Intl.NumberFormat.
Example Use Cases
60,000 m² reforested
25,000 supporters reached
€1,200,000 raised
30% engagement increase