
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
@uekichinos/counter
Advanced tools
Lightweight counter animation library that detects and animates all numbers within any string — including integers, decimals, and comma-formatted values like '1,500,344' or '34.67%'. Supports scroll-triggered or immediate start, repeat on re-entry, custom
Lightweight counter animation library that detects and animates all numbers within any string — including integers, decimals, and comma-formatted values.
"We have 1,500,344 customers" → counts up from 0 to 1,500,344
"34.67%" → counts up from 0.00% to 34.67%
"RM55 billion" → counts up from RM0 to RM55 billion
pause / resume / restart / update / destroyupdate() animates from the current value to the nextprefers-reduced-motion<script> tagnpm install @uekichinos/counter
Add data-counter to any element. Call initCounters once.
<p data-counter>RM55 billion</p>
<p data-counter>34.67%</p>
<p data-counter>We have 1,500,344 customers</p>
<script type="module">
import { initCounters } from '@uekichinos/counter'
initCounters('[data-counter]')
// scroll-triggered, animates once, 2s easeOut — all by default
</script>
import { animateCounter } from '@uekichinos/counter'
const el = document.querySelector('#revenue')
animateCounter(el, { duration: 3000, trigger: 'scroll' })
<script> tag (no bundler)<script src="https://unpkg.com/@uekichinos/counter/dist/index.global.js"></script>
<script>
Counter.initCounters('[data-counter]')
</script>
initCounters(selector, options?)Finds all elements matching selector and animates them. Best for declarative HTML setups.
initCounters('[data-counter]', {
duration: 2000,
trigger: 'scroll',
})
Per-element overrides are supported via data attributes:
<p data-counter data-counter-duration="4000" data-counter-trigger="immediate" data-counter-repeat="true">
99.99%
</p>
| Data attribute | Overrides |
|---|---|
data-counter-duration | duration |
data-counter-trigger | trigger |
data-counter-repeat | repeat |
data-counter-decimals | decimals |
Returns a CounterHandle[] — one handle per matched element.
animateCounter(el, options?)Animates a single element and returns a CounterHandle.
const counter = animateCounter(document.querySelector('#stat'), {
duration: 2000,
easing: 'easeOut',
trigger: 'scroll',
onComplete: () => console.log('done'),
})
CounterHandle| Method / prop | Description |
|---|---|
pause() | Freeze at the current frame, keeping progress |
resume() | Continue a paused animation |
restart() | Replay from startValue to the current target |
update(next) | Animate from the currently displayed value(s) to the numbers in next (string or number) |
destroy() | Stop the animation and disconnect all observers |
isAnimating | true while an animation is in progress |
// Live dashboard — feed new values as they arrive
const c = animateCounter(el, { trigger: 'immediate' })
socket.on('online', (n) => c.update(`${n} users online`))
// Clean up on unmount (removes the IntersectionObserver)
onUnmount(() => c.destroy())
| Option | Type | Default | Description |
|---|---|---|---|
duration | number | 2000 | Animation duration in milliseconds |
easing | 'linear' | 'easeOut' | 'easeInOut' | (t) => number | 'easeOut' | Named preset or a custom easing function |
trigger | 'scroll' | 'immediate' | 'scroll' | When to start — on scroll into view, or right away |
repeat | boolean | false | Re-animate each time the element re-enters the viewport |
threshold | number | 0.2 | How much of the element must be visible to trigger (0–1) |
startValue | number | 0 | Value to start from — may be above the target to count down |
decimals | number | — | Force a fixed decimal count, overriding what the source implies (5 → 5.00) |
separator | string | ',' | Grouping separator; when set, grouping is applied even if the source had none |
decimal | string | '.' | Decimal point character |
numerals | string[] (length 10) | — | Glyphs for digits 0–9 (Arabic-Indic, Devanagari, …) |
smartEasingThreshold | number | — | Above this range, animate the bulk linearly and ease only the tail |
smartEasingAmount | number | 300 | Units eased at the tail when smart easing applies |
formattingFn | (value: number) => string | — | Full control of the rendered string (overrides decimals/separator/decimal/numerals) |
watch | boolean | false | Animate to the new value whenever the element's text is changed by other code |
onStart | () => void | — | Called once when the animation starts (on scroll-in for trigger: 'scroll') |
onComplete | () => void | — | Called once when the animation finishes |
All numbers in the string are detected and animate simultaneously.
<p data-counter>
From 12 offices across 48 countries, we serve 3,200,000 users daily.
</p>
animateCounter(el, { startValue: 1200, duration: 1000 })
// counts from 1,200 → target
animateCounter(el, { startValue: 100 }) // el reads "0" → counts 100 → 0
const c = animateCounter(document.querySelector('#online'), { trigger: 'immediate' })
// each push animates from the currently shown number to the new one
eventSource.onmessage = (e) => c.update(`${e.data} online`)
animateCounter(el, {
easing: 'easeOut',
smartEasingThreshold: 1000, // ranges above 1,000 …
smartEasingAmount: 500, // … ease only the final 500
})
animateCounter(el, {
separator: ' ',
decimal: ',',
numerals: ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'],
})
animateCounter(el, { formattingFn: (v) => `$${v.toFixed(2)}` })
animateCounter(el, { watch: true })
// later, anywhere: el.textContent = '9,001' → counter animates to it
animateCounter(el, {
onComplete: () => {
document.querySelector('#badge').classList.add('visible')
},
})
<p data-counter data-counter-repeat="true">99.99%</p>
<p data-counter data-counter-trigger="immediate">1,500,344</p>
If the user has enabled Reduce Motion in their OS settings, all animations are skipped and the final value is displayed immediately. The onStart and onComplete callbacks still fire.
| Input string | Detected number |
|---|---|
RM55 billion | 55 |
RM 55 million | 55 |
34.67% | 34.67 |
We have 1,500,344 customers | 1500344 |
$1,234.56 total | 1234.56 |
From 12 offices across 48 countries | 12, 48 |
1000 / 1234567 | 1000 / 1234567 (one number each) |
Comma grouping and decimal places are preserved throughout the animation.
MIT © uekichinos
FAQs
Lightweight counter animation library that detects and animates all numbers within any string — including integers, decimals, and comma-formatted values like '1,500,344' or '34.67%'. Supports scroll-triggered or immediate start, repeat on re-entry, custom
The npm package @uekichinos/counter receives a total of 42 weekly downloads. As such, @uekichinos/counter popularity was classified as not popular.
We found that @uekichinos/counter demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.