New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

@uekichinos/counter

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@uekichinos/counter

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

latest
Source
npmnpm
Version
0.2.0
Version published
Weekly downloads
49
-81.15%
Maintainers
1
Weekly downloads
 
Created
Source

@uekichinos/counter

Socket Badge

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
  • Zero dependencies — plain DOM, no frameworks required
  • Any string format — surrounding text is preserved, only numbers animate
  • Control handlepause / resume / restart / update / destroy
  • Live updatesupdate() animates from the current value to the next
  • Scroll-triggered or immediate — via Intersection Observer
  • Accessibility-aware — respects prefers-reduced-motion
  • Works everywhere — ESM, CommonJS, or <script> tag

Installation

npm install @uekichinos/counter

Quick start

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>

Programmatic

import { animateCounter } from '@uekichinos/counter'

const el = document.querySelector('#revenue')
animateCounter(el, { duration: 3000, trigger: 'scroll' })

Via <script> tag (no bundler)

<script src="https://unpkg.com/@uekichinos/counter/dist/index.global.js"></script>
<script>
  Counter.initCounters('[data-counter]')
</script>

API

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 attributeOverrides
data-counter-durationduration
data-counter-triggertrigger
data-counter-repeatrepeat
data-counter-decimalsdecimals

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 / propDescription
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
isAnimatingtrue 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())

Options

OptionTypeDefaultDescription
durationnumber2000Animation 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
repeatbooleanfalseRe-animate each time the element re-enters the viewport
thresholdnumber0.2How much of the element must be visible to trigger (0–1)
startValuenumber0Value to start from — may be above the target to count down
decimalsnumberForce a fixed decimal count, overriding what the source implies (55.00)
separatorstring','Grouping separator; when set, grouping is applied even if the source had none
decimalstring'.'Decimal point character
numeralsstring[] (length 10)Glyphs for digits 09 (Arabic-Indic, Devanagari, …)
smartEasingThresholdnumberAbove this range, animate the bulk linearly and ease only the tail
smartEasingAmountnumber300Units eased at the tail when smart easing applies
formattingFn(value: number) => stringFull control of the rendered string (overrides decimals/separator/decimal/numerals)
watchbooleanfalseAnimate to the new value whenever the element's text is changed by other code
onStart() => voidCalled once when the animation starts (on scroll-in for trigger: 'scroll')
onComplete() => voidCalled once when the animation finishes

Examples

Multiple numbers in one string

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>

Animate from a previous value

animateCounter(el, { startValue: 1200, duration: 1000 })
// counts from 1,200 → target

Count down

animateCounter(el, { startValue: 100 })   // el reads "0" → counts 100 → 0

Live value updates

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`)

Smart easing for very large numbers

animateCounter(el, {
  easing: 'easeOut',
  smartEasingThreshold: 1000,   // ranges above 1,000 …
  smartEasingAmount: 500,       // … ease only the final 500
})

Localised digits and separators

animateCounter(el, {
  separator: ' ',
  decimal: ',',
  numerals: ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'],
})

Custom formatting

animateCounter(el, { formattingFn: (v) => `$${v.toFixed(2)}` })

Auto-follow external text changes

animateCounter(el, { watch: true })
// later, anywhere:  el.textContent = '9,001'  →  counter animates to it

Run something after the count finishes

animateCounter(el, {
  onComplete: () => {
    document.querySelector('#badge').classList.add('visible')
  },
})

Repeat on every scroll-in

<p data-counter data-counter-repeat="true">99.99%</p>

Immediate trigger (no scroll)

<p data-counter data-counter-trigger="immediate">1,500,344</p>

Accessibility

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.

Number formats supported

Input stringDetected number
RM55 billion55
RM 55 million55
34.67%34.67
We have 1,500,344 customers1500344
$1,234.56 total1234.56
From 12 offices across 48 countries12, 48
1000 / 12345671000 / 1234567 (one number each)

Comma grouping and decimal places are preserved throughout the animation.

License

MIT © uekichinos

Keywords

uekichinos

FAQs

Package last updated on 05 Sep 2026

Related posts