data-ticker

Animated number count-up on scroll. Add data-ticker to any <data> element for smooth number animation using easeOutExpo easing.

Overview

The data-ticker attribute turns a static <data> element into an animated counter. Numbers count up from zero (or a custom start) to the value attribute when the element enters the viewport. The original text content serves as the no-JS fallback.

<data value="24521" data-ticker>24,521</data>

How It Works

  1. An IntersectionObserver watches for the element to enter the viewport (10% threshold)
  2. When visible, a requestAnimationFrame loop animates from start to end value
  3. Each frame applies easeOutExpo easing for a fast-start, decelerating finish
  4. Intermediate values are formatted with Intl.NumberFormat matching any data-format-number options
  5. At completion, sets data-format-number-init to prevent double-formatting

The animation runs at 60fps using requestAnimationFrame and respects prefers-reduced-motion.

Attributes

Attribute Type Description
data-ticker string Animation duration in ms. Empty or omitted defaults to 1000.
value string The target numeric value. Standard HTML value attribute on <data>.
data-ticker-start string Start value for the animation. Defaults to "0".
data-ticker-decimals string Decimal places during animation. Auto-detected from value if omitted (e.g., "99.99" = 2).
data-locale string Optional locale override (e.g., "de-DE"). Shared with data-format-number.
data-ticker-init boolean Set automatically to prevent double-binding. Do not set manually.

Duration

Control animation speed with the data-ticker value. The value is duration in milliseconds:

1s (default)
100
2s
5,000
3.5s
99,999
<!-- 1 second (default) --> <data value="100" data-ticker>100</data> <!-- 2 seconds --> <data value="5000" data-ticker="2000">5,000</data> <!-- 3.5 seconds --> <data value="99999" data-ticker="3500">99,999</data>

Start Value

By default, the ticker counts from 0. Use data-ticker-start to begin from a different value:

Counts from 500
1,000
<!-- Count from 500 to 1000 --> <data value="1000" data-ticker data-ticker-start="500">1,000</data>

Decimal Precision

Decimal places are auto-detected from the value attribute. Use data-ticker-decimals to override:

Auto (2 decimals)
99.99
Override (1 decimal)
99.99
<!-- Auto-detected from value: 2 decimal places --> <data value="99.99" data-ticker>99.99</data> <!-- Explicit override: 1 decimal place --> <data value="99.99" data-ticker data-ticker-decimals="1">99.99</data>

With Format Number

Combine with data-format-number for locale-aware formatting during the animation. The ticker reads the format style and applies matching Intl.NumberFormat options to intermediate values:

Currency
$48,200
Compact
1.5M
<!-- Currency formatting during animation --> <data value="48200" data-ticker="2000" data-format-number="currency" data-currency="USD">$48,200</data> <!-- Compact notation --> <data value="1500000" data-ticker="2500" data-format-number="compact">1.5M</data> <!-- Percent --> <data value="0.85" data-ticker="1500" data-format-number="percent">85%</data>

At animation completion, the ticker sets data-format-number-init to prevent the format-number observer from re-processing the element.

Easing

The animation uses easeOutExpo easing, which starts fast and decelerates toward the end. This creates the classic "ticker" feel where numbers roll quickly then settle on the final value.

// easeOutExpo — fast start, decelerating finish function easeOutExpo(t) { return t === 1 ? 1 : 1 - Math.pow(2, -10 * t); }

Reduced Motion

The ticker respects the user's motion preference. When reduced motion is detected, the animation is skipped entirely and the original text content is preserved:

/* The ticker checks for reduced motion preference: * * 1. prefers-reduced-motion: reduce (OS setting) * 2. data-motion-reduced attribute on <html> * * If either is true, the animation is skipped entirely. * The original text content is preserved as the no-JS fallback, * and data-format-number handles formatting normally. */
  • Checks prefers-reduced-motion: reduce (OS setting)
  • Checks data-motion-reduced attribute on <html>
  • If either is true, the ticker does nothing — text stays as-is
  • data-format-number still runs normally for formatting

Stats Pattern Integration

The ticker pairs naturally with the Stats pattern. Add data-ticker to stat values for animated reveals:

Active Users
24,521
Revenue
$48,200
Uptime %
99.9
<section class="stats"> <figure> <figcaption>Active Users</figcaption> <data value="24521" data-ticker data-format-number>24,521</data> </figure> <figure> <figcaption>Revenue</figcaption> <data value="48200" data-ticker="2000" data-format-number="currency" data-currency="USD">$48,200</data> </figure> <figure> <figcaption>Uptime %</figcaption> <data value="99.9" data-ticker="1500" data-ticker-decimals="1">99.9</data> </figure> </section>

Dynamic Elements

Elements added to the DOM after page load are automatically observed via a MutationObserver. No manual initialization is needed.

// Dynamically added tickers are auto-observed via MutationObserver const el = document.createElement('data'); el.value = '5000'; el.dataset.ticker = '2000'; el.textContent = '5,000'; document.body.appendChild(el); // el is observed automatically — no manual init needed

Accessibility

  • The original text content serves as a no-JS fallback
  • The semantic <data> element with value attribute provides machine-readable data at all times
  • prefers-reduced-motion is respected — no animation when the user requests it
  • font-variant-numeric: tabular-nums prevents layout shift during animation
  • No wrapper element — the <data> is the enhanced element
  • Screen readers see the final value via the value attribute, not the animated intermediate values