
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
@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 33 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
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

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.