A high-performance, feature-rich animated counter library with smart overflow prevention and viewport triggering.
- ๐ High Performance: Optimized with caching, batching, and requestAnimationFrame
- ๐ฑ Responsive Design: Smart overflow detection with automatic compact formatting
- ๐ Viewport Triggering: Counters animate when they come into view
- ๐จ Flexible Formatting: Thousand separators, compact notation (K/M/B/T), decimals
- โก Zero Dependencies: Pure vanilla JavaScript, works everywhere
- ๐ง Easy Integration: Simple data attributes or JavaScript API
<script src="counter-system.js"></script><!-- Simple counter -->
<div data-counter data-end="1000">0</div>
<!-- Advanced counter -->
<div data-counter
data-start="0"
data-end="5000000"
data-duration="3000"
data-prefix="$"
data-suffix="+"
data-decimal="1"
data-compact="true">0</div>Counters automatically initialize and animate when they enter the viewport!
<!-- Basic counter from 0 to 1000 -->
<div data-counter data-end="1000">0</div>
<!-- Revenue counter with formatting -->
<div data-counter
data-end="2500000"
data-prefix="$"
data-compact="true"
data-duration="3000">0</div>
<!-- Downloads with overflow prevention -->
<div data-counter
data-end="15750"
data-prevent-overflow="true"
data-max-width="100">0</div>
<!-- Percentage with decimals -->
<div data-counter
data-end="99.9"
data-suffix="%"
data-decimal="1">0</div>// Initialize all counters
CounterSystem.init();
// Run all counters immediately
CounterSystem.runAll();
// Animate specific counter
CounterSystem.animate('#myCounter', {
start: 0,
end: 1000,
duration: 2000,
prefix: '$',
compact: true
});
// Animate multiple counters with stagger
CounterSystem.animateBatch(['.counter1', '.counter2', '.counter3'], {
duration: 3000,
compact: true
});
// Setup viewport triggering manually
const observer = CounterSystem.observe();
// Direct counter animation
CounterSystem.animateCounter(element, options);| Attribute | Type | Default | Description |
|---|---|---|---|
data-counter |
- | - | Required. Identifies element as counter |
data-start |
Number | 0 |
Starting number |
data-end |
Number | - | Required. Ending number |
data-duration |
Number | 2000 |
Animation duration (ms) |
data-decimal |
Number | 0 |
Decimal places |
data-prefix |
String | '' |
Text before number |
data-suffix |
String | '' |
Text after number |
data-format |
Boolean | false |
Add thousand separators |
data-compact |
Boolean | false |
Use K/M/B/T format |
data-prevent-overflow |
Boolean | false |
Auto-compact on overflow |
data-max-width |
Number | null |
Max width (px) for overflow |
const options = {
start: 0, // Starting number
end: 1000, // Ending number
duration: 2000, // Animation duration (ms)
decimal: 0, // Decimal places
prefix: '', // Text before number
suffix: '', // Text after number
format: false, // Thousand separators
compact: false, // K/M/B format
preventOverflow: false, // Auto-compact on overflow
maxWidth: null // Max width for overflow detection
};1,500 โ 1.5K
2,500,000 โ 2.5M
12,300,000,000 โ 12.3B
1,750,000,000,000 โ 1.8T1000 โ 1,000
1000000 โ 1,000,000<!-- Will show as "$2.5M+" -->
<div data-counter
data-end="2500000"
data-prefix="$"
data-suffix="+"
data-compact="true">0</div>// Disable auto-initialization by commenting out autoInit() in the script
// Then manually control:
CounterSystem.initialize();
// Or just specific functions:
CounterSystem.init();
const observer = CounterSystem.observe();// Check cache usage
console.log(CounterSystem.getCacheSize());
// Clear cache if needed
CounterSystem.clearCache();// Wait for specific event
document.addEventListener('myCustomEvent', () => {
CounterSystem.runAll();
});
// Animate on scroll to specific position
window.addEventListener('scroll', () => {
if (window.scrollY > 500) {
CounterSystem.animate('.hero-counter');
}
});.counter {
min-width: 100px; /* Prevent width changes during animation */
text-align: right; /* Align numbers nicely */
}.counter {
font-variant-numeric: tabular-nums; /* Monospace numbers */
transition: opacity 0.3s ease;
}.counter {
font-size: clamp(1rem, 4vw, 3rem);
}
@media (max-width: 768px) {
.counter {
/* Enable compact format on mobile */
}
}- Text Measurement Caching: Reuses width calculations
- Computed Style Caching: Minimizes expensive style lookups
- Batch Processing: Animates multiple counters efficiently
- Intersection Observer: Only animates visible counters
- RequestAnimationFrame: Smooth 60fps animations
- Smart Formatting: Pre-compiled regexes and optimized math
- Modern Browsers: Chrome 51+, Firefox 55+, Safari 12.1+, Edge 79+
- Features Used: IntersectionObserver, requestAnimationFrame, WeakMap
- Fallbacks: Graceful degradation for older browsers
CounterSystem.init()- Initialize all countersCounterSystem.runAll()- Start all counter animationsCounterSystem.observe()- Setup viewport triggeringCounterSystem.animate(selector, options)- Animate single counterCounterSystem.animateBatch(selectors, options)- Animate multiple counters
CounterSystem.formatCompact(number, decimals)- Format number compactlyCounterSystem.formatNumber(number, options)- Format with optionsCounterSystem.clearCache()- Clear performance cacheCounterSystem.getCacheSize()- Get cache statistics
- Fork the repository
- Create your feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
MIT License - feel free to use in personal and commercial projects.
- TypeScript definitions
- CSS-only fallback option
- Additional easing functions
- Accessibility improvements (ARIA labels)
- React/Vue component wrappers
Made with โค๏ธ for developers who need beautiful, performant counters