Check out the live site: https://spiddyyy.github.io/counter-scroll/
A powerful, lightweight, and highly customizable JavaScript library for animated scroll counters. Perfect for showcasing statistics, metrics, and numbers with smooth scroll-triggered animations.
- ๐ฏ Scroll-triggered animations - IntersectionObserver with fallback support
- ๐ฐ Currency formatting - Built-in currency symbols and separators
- ๐ Smart abbreviations - Automatic K/M/B/T formatting for large numbers
- ๐ข Decimal precision - Support for any number of decimal places
- ๐ Advanced easing - 15+ easing functions including bounce, back, and elastic
- ๐ฎ Manual controls - Programmatic trigger, reset, and control
- ๐ฑ Performance optimized - Throttled events and memory cleanup
- ๐ง Developer friendly - TypeScript ready, callbacks, and flexible configuration
- ๐ฆ Zero dependencies - Pure vanilla JavaScript
- ๐ Universal support - Works with all module systems (ES6, CommonJS, AMD, Global)
<script src="https://cdn.jsdelivr.net/gh/Spiddyyy/counter-scroll@1.0.0v/scroll-counter.min.js"></script>npm install scrollcounteryarn add scrollcounter<div class="counter-value" data-count="1250">0</div>
<div class="counter-value" data-count="99.9" data-decimals="1" data-suffix="%">0%</div>
<div class="counter-value" data-count="1500000" data-currency="true" data-abbreviate="true">$0</div>// Simple initialization
const counter = new ScrollCounter();
// Advanced configuration
const counter = new ScrollCounter('.counter-value', {
duration: 2000,
easing: 'easeOutQuart',
decimals: 0,
currency: false,
onComplete: (element, value) => {
console.log('Animation completed!', value);
}
});| Option | Type | Default | Description |
|---|---|---|---|
duration |
Number | 2000 |
Animation duration in milliseconds |
easing |
String | 'easeOutQuart' |
Animation easing function |
once |
Boolean | true |
Animate only once when scrolled into view |
replay |
Boolean | false |
Re-animate when scrolling back into view |
decimals |
Number | 0 |
Number of decimal places to show |
separator |
String | ',' |
Thousands separator character |
decimal |
String | '.' |
Decimal point character |
prefix |
String | '' |
Text to prepend to the number |
suffix |
String | '' |
Text to append to the number |
currency |
Boolean | false |
Enable currency formatting |
currencySymbol |
String | '$' |
Currency symbol to use |
abbreviate |
Boolean | false |
Use K/M/B/T abbreviations for large numbers |
useGrouping |
Boolean | true |
Add thousand separators |
step |
Number | 1 |
Increment step (e.g., count by 5s, 10s) |
onStart |
Function | null |
Callback when animation starts |
onUpdate |
Function | null |
Callback during animation |
onComplete |
Function | null |
Callback when animation completes |
'linear', 'easeIn', 'easeOut', 'easeInOut',
'easeInQuad', 'easeOutQuad', 'easeInOutQuad',
'easeInCubic', 'easeOutCubic', 'easeInOutCubic',
'easeInQuart', 'easeOutQuart', 'easeInOutQuart',
'easeInQuint', 'easeOutQuint', 'easeInOutQuint',
'easeInSine', 'easeOutSine', 'easeInOutSine',
'easeInExpo', 'easeOutExpo', 'easeInOutExpo',
'easeInBack', 'easeOutBack', 'easeInOutBack',
'bounce'Override global options with HTML data attributes:
| Attribute | Example | Description |
|---|---|---|
data-count |
data-count="1250" |
Target number to count to |
data-duration |
data-duration="3000" |
Animation duration |
data-easing |
data-easing="bounce" |
Easing function |
data-decimals |
data-decimals="2" |
Decimal places |
data-prefix |
data-prefix="$" |
Prefix text |
data-suffix |
data-suffix="%" |
Suffix text |
data-separator |
data-separator="." |
Thousands separator |
data-decimal |
data-decimal="," |
Decimal point |
data-currency |
data-currency="true" |
Enable currency mode |
data-currency-symbol |
data-currency-symbol="โฌ" |
Currency symbol |
data-abbreviate |
data-abbreviate="true" |
Enable abbreviations |
data-grouping |
data-grouping="false" |
Disable grouping |
data-step |
data-step="5" |
Count increment |
data-threshold |
data-threshold="0.8" |
Visibility threshold (0-1) |
<div class="counter-value" data-count="1250">0</div><div class="counter-value"
data-count="1599.99"
data-currency="true"
data-decimals="2">$0.00</div><div class="counter-value"
data-count="1500000"
data-abbreviate="true"
data-currency="true">$0</div>
<!-- Result: $1.5M --><div class="counter-value"
data-count="95"
data-suffix="%"
data-easing="bounce"
data-duration="3000">0%</div><div class="counter-value"
data-count="1234.56"
data-separator="."
data-decimal=","
data-decimals="2">0,00</div>
<!-- Result: 1.234,56 --><div class="counter-value"
data-count="100"
data-step="5">0</div>
<!-- Counts: 0, 5, 10, 15, 20... 100 -->const counter = new ScrollCounter();
// Trigger all counters
counter.trigger();
// Trigger specific counters
counter.trigger('.stats-counter');
// Reset all counters
counter.reset();
// Reset specific counters
counter.reset('.stats-counter');
// Cleanup (remove observers and events)
counter.destroy();// One-liner initialization
const counter = ScrollCounter.init('.counter-value', {
duration: 3000,
easing: 'easeOutBack'
});const counter = new ScrollCounter('.counter-value', {
duration: 2000,
easing: 'easeOutQuart',
onStart: (element, startValue, targetValue) => {
element.style.color = '#007bff';
console.log(`Starting animation from ${startValue} to ${targetValue}`);
},
onUpdate: (element, currentValue, progress) => {
// Update progress bar, change colors, etc.
const opacity = 0.5 + (progress * 0.5);
element.style.opacity = opacity;
},
onComplete: (element, finalValue) => {
element.style.color = '#28a745';
console.log(`Animation completed! Final value: ${finalValue}`);
}
});// Different configurations for different sections
const statsCounters = new ScrollCounter('.stats-counter', {
duration: 3000,
easing: 'easeOutBack',
currency: true,
abbreviate: true
});
const achievementCounters = new ScrollCounter('.achievement-counter', {
duration: 1500,
easing: 'bounce',
suffix: '%'
});import ScrollCounter from 'scrollcounter';
const counter = new ScrollCounter();const ScrollCounter = require('scrollcounter');
const counter = new ScrollCounter();define(['scrollcounter'], function(ScrollCounter) {
const counter = new ScrollCounter();
});- IntersectionObserver - Efficient scroll detection with automatic fallback
- Throttled events - Optimized scroll event handling
- Memory management - Proper cleanup of observers and event listeners
- Animation prevention - Prevents overlapping animations on same elements
- Minimal DOM queries - Cached selectors and optimized queries
- Modern browsers - Full IntersectionObserver support
- Legacy browsers - Automatic fallback to scroll events
- Mobile optimized - Touch-friendly and performant on mobile devices
- IE11+ - With polyfill for IntersectionObserver
import { useEffect, useRef } from 'react';
import ScrollCounter from 'scrollcounter';
function useScrollCounter(options = {}) {
const ref = useRef();
useEffect(() => {
if (ref.current) {
const counter = new ScrollCounter(ref.current, options);
return () => counter.destroy();
}
}, []);
return ref;
}
// Usage
function StatsComponent() {
const counterRef = useScrollCounter({ duration: 3000 });
return (
<div ref={counterRef} className="counter-value" data-count="1250">
0
</div>
);
}// Vue directive
app.directive('scroll-counter', {
mounted(el, binding) {
new ScrollCounter(el, binding.value || {});
}
});
// Usage in template
<div v-scroll-counter="{ duration: 3000 }" class="counter-value" data-count="1250">0</div>For immediate use without installation:
<!DOCTYPE html>
<html>
<head>
<title>ScrollCounter Demo</title>
</head>
<body>
<!-- Your counter elements -->
<div class="counter-value" data-count="1250">0</div>
<div class="counter-value" data-count="99" data-suffix="%">0%</div>
<!-- Include library -->
<script src="https://cdn.jsdelivr.net/gh/Spiddyyy/counter-scroll@1.0.0v/scroll-counter.min.js"></script>
<!-- Initialize -->
<script>
const counter = new ScrollCounter();
</script>
</body>
</html>We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- ๐ง Email: support@scrollcounter.dev
- ๐ Issues: GitHub Issues
- ๐ฌ Discussions: GitHub Discussions
- ๐ Documentation: Full Documentation
- ๐ Live Demo: https://scrollcounter.dev/demo
- ๐ CodePen Collection: ScrollCounter Examples
- ๐ฎ Interactive Playground: Try it online
โญ If you found ScrollCounter helpful, please give it a star on GitHub! โญ
Made with โค๏ธ for the developer community
Demo โข Documentation โข GitHub