Counter Effect

Animated number counting with easing, formatting, and scroll-triggered activation. Perfect for stats and dashboards.

Domma.effects.counter()

The counter() effect smoothly counts from one number to another using requestAnimationFrame with configurable easing. Supports thousands separators, prefixes, suffixes, decimals, and scroll-triggered activation.

// Count from 0 to the number in the element
Domma.effects.counter('.stat-number');

// With full formatting
Domma.effects.counter('.revenue', {
  from: 0,
  to: 1250000,
  duration: 3000,
  separator: ',',
  prefix: '$',
  decimals: 2,
  easing: 'ease-out'
});

// Scroll-triggered
Domma.effects.counter('.stat', {
  trigger: 'scroll',
  threshold: 0.5
});

Demo: Dashboard Stats

Click to animate stat counters with staggered timing.

Demo: Dashboard Stats
12500
Users
847
Projects
99
Uptime %
3200000
API Calls
<div class="stat-card">
    <div class="stat-number counter-demo-1">12500</div>
    <div class="stat-label">Users</div>
</div>
<div class="stat-card">
    <div class="stat-number counter-demo-1">847</div>
    <div class="stat-label">Projects</div>
</div>
<div class="stat-card">
    <div class="stat-number counter-demo-1">99</div>
    <div class="stat-label">Uptime %</div>
</div>
<div class="stat-card">
    <div class="stat-number counter-demo-1">3200000</div>
    <div class="stat-label">API Calls</div>
</div>
<button type="button" class="btn btn-primary" id="counter-start-1">Start Count</button>
<button type="button" class="btn btn-danger" id="counter-restart-1">Restart</button>
/**
 * Demo: Dashboard Stats - from public/showcase/effects/counter.
 */
export function mount(root) {
        // Store original values for restart
    const originals1 = [];
    $('.counter-demo-1').each((i, el) => originals1.push(el.textContent));
    let ctrl1;
        // Demo 1: Dashboard Stats
    $(root).find('#counter-start-1').on('click', () => {
          if (ctrl1) ctrl1.destroy();
          $('.counter-demo-1').each((i, el) => { el.textContent = originals1[i]; });
          ctrl1 = Domma.effects.counter('.counter-demo-1', {
            duration: 2000,
            stagger: 200,
            separator: ',',
            respectMotionPreference: false
          });
        });
    $(root).find('#counter-restart-1').on('click', () => {
          if (ctrl1) ctrl1.restart();
        });
}
Domma.effects.counter('.stat-number', {
  duration: 2000,
  stagger: 200,
  separator: ','
});

Demo: Prefix & Suffix

Add currency symbols, percentage signs, and other formatting.

Demo: Prefix & Suffix
0
Revenue
0
Satisfaction
0
Temperature
<div class="stat-card">
    <div class="stat-number" id="counter-revenue" style="font-size: 2.5rem;">0</div>
    <div class="stat-label">Revenue</div>
</div>
<div class="stat-card">
    <div class="stat-number" id="counter-percent" style="font-size: 2.5rem;">0</div>
    <div class="stat-label">Satisfaction</div>
</div>
<div class="stat-card">
    <div class="stat-number" id="counter-temp" style="font-size: 2.5rem;">0</div>
    <div class="stat-label">Temperature</div>
</div>
<button type="button" class="btn btn-primary mt-3" id="counter-start-2">Start</button>
/**
 * Demo: Prefix & Suffix - from public/showcase/effects/counter.
 */
export function mount(root) {
        // Demo 2: Prefix & Suffix
    $(root).find('#counter-start-2').on('click', () => {
          Domma.effects.counter('#counter-revenue', {
            to: 84750, prefix: '$', separator: ',', duration: 2000,
            respectMotionPreference: false
          });
          Domma.effects.counter('#counter-percent', {
            to: 98.7, suffix: '%', decimals: 1, duration: 2000,
            respectMotionPreference: false
          });
          Domma.effects.counter('#counter-temp', {
            to: 23.5, suffix: '°C', decimals: 1, duration: 2000,
            respectMotionPreference: false
          });
        });
}
Domma.effects.counter('#revenue', {
  to: 84750, prefix: '$', separator: ','
});

Domma.effects.counter('#percent', {
  to: 98.7, suffix: '%', decimals: 1
});

Domma.effects.counter('#temp', {
  to: 23.5, suffix: '°C', decimals: 1
});

Demo: Easing Comparison

Compare different easing functions counting to 1,000.

Demo: Easing Comparison
0
Linear
0
Ease Out
0
Ease In-Out
<div class="stat-card">
    <div class="stat-number" id="counter-linear" style="font-size: 2.5rem;">0</div>
    <div class="stat-label">Linear</div>
</div>
<div class="stat-card">
    <div class="stat-number" id="counter-easeout" style="font-size: 2.5rem;">0</div>
    <div class="stat-label">Ease Out</div>
</div>
<div class="stat-card">
    <div class="stat-number" id="counter-easeinout" style="font-size: 2.5rem;">0</div>
    <div class="stat-label">Ease In-Out</div>
</div>
<button type="button" class="btn btn-primary mt-3" id="counter-start-3">Start All</button>
/**
 * Demo: Easing Comparison - from public/showcase/effects/counter.
 */
export function mount(root) {
        // Demo 3: Easing Comparison
    $(root).find('#counter-start-3').on('click', () => {
          $(root).find('#counter-linear').text('0');
          $(root).find('#counter-easeout').text('0');
          $(root).find('#counter-easeinout').text('0');
          Domma.effects.counter('#counter-linear', {
            to: 1000, easing: 'linear', duration: 3000, separator: ',',
            respectMotionPreference: false
          });
          Domma.effects.counter('#counter-easeout', {
            to: 1000, easing: 'ease-out', duration: 3000, separator: ',',
            respectMotionPreference: false
          });
          Domma.effects.counter('#counter-easeinout', {
            to: 1000, easing: 'ease-in-out', duration: 3000, separator: ',',
            respectMotionPreference: false
          });
        });
}
Domma.effects.counter('#linear', { to: 1000, easing: 'linear', duration: 3000 });
Domma.effects.counter('#easeout', { to: 1000, easing: 'ease-out', duration: 3000 });
Domma.effects.counter('#easeinout', { to: 1000, easing: 'ease-in-out', duration: 3000 });

Demo: Scroll Triggered

These counters start when they scroll into view. Scroll down if they haven't started yet.

Demo: Scroll Triggered
5280
Downloads
142
Contributors
28
Countries
<div class="stat-card">
    <div class="stat-number counter-scroll">5280</div>
    <div class="stat-label">Downloads</div>
</div>
<div class="stat-card">
    <div class="stat-number counter-scroll">142</div>
    <div class="stat-label">Contributors</div>
</div>
<div class="stat-card">
    <div class="stat-number counter-scroll">28</div>
    <div class="stat-label">Countries</div>
</div>
/**
 * Demo: Scroll Triggered - from public/showcase/effects/counter.
 */
export function mount(root) {
        // Demo 4: Scroll triggered
    Domma.effects.counter('.counter-scroll', {
          trigger: 'scroll',
          threshold: 0.5,
          stagger: 150,
          separator: ',',
          respectMotionPreference: false
        });
}
Domma.effects.counter('.counter-scroll', {
  trigger: 'scroll',
  threshold: 0.5,
  stagger: 150,
  separator: ','
});

Demo: Decimal Precision

Count with decimal places for financial or scientific data.

Demo: Decimal Precision
0
Portfolio Value
<div class="stat-number" id="counter-decimal" style="font-size: 2.5rem;">0</div>
<div class="stat-label">Portfolio Value</div>
<button type="button" class="btn btn-primary mt-3" id="counter-start-decimal">Start</button>
/**
 * Demo: Decimal Precision - from public/showcase/effects/counter.
 */
export function mount(root) {
        // Demo 5: Decimal
    $(root).find('#counter-start-decimal').on('click', () => {
          $(root).find('#counter-decimal').text('0');
          Domma.effects.counter('#counter-decimal', {
            to: 42867.53,
            prefix: '$',
            decimals: 2,
            separator: ',',
            duration: 2500,
            respectMotionPreference: false
          });
        });
}
Domma.effects.counter('#counter-decimal', {
  to: 42867.53,
  prefix: '$',
  decimals: 2,
  separator: ',',
  duration: 2500
});

Options Reference

Option Type Default Description
from number 0 Start value
to number null End value (reads from element if null)
duration number 2000 Total count duration in ms
easing string 'ease-out' 'linear', 'ease-out', 'ease-in-out'
decimals number 0 Decimal places
separator string ',' Thousands separator
prefix string '' Text before number
suffix string '' Text after number
stagger number 0 Delay between elements (ms)
trigger string 'immediate' 'immediate' or 'scroll'
threshold number 0.5 Observer threshold when trigger is scroll
respectMotionPreference boolean true Honour prefers-reduced-motion
onUpdate function null Callback on each frame (currentValue)
onComplete function null Callback when counting completes