
Number Count
Numbers count up as soon as they come into view — in exactly the notation already written in the Designer.
The target value is not configured, it is read. Whatever stands in the Designer as text — 1.234, 1,234, 1'234 or 1 234, with “€ ” in front or “ Mio.” behind — is taken apart: the number for the animation, everything else as the shape it returns to. That is why the final state matches the Designer text exactly, and when the number changes in the CMS, the counter changes with it.
- ReadThe text content is parsed: thousands separator, decimal separator, prefix and suffix are recognised and preserved.
- WaitAn IntersectionObserver at a threshold of 0.4 waits until the number is in view — once, unless it is meant to repeat.
- CountThe animation runs via requestAnimationFrame with easeOutCubic; formatting goes through Intl.NumberFormat using the language from <html lang>.
Add this <script> to your project's custom code
Once per project is enough, even if you use the solution on several pages: Project Settings → Custom Code → Head Code. The script loads alongside the page without holding up rendering; it runs once the page is in place and does nothing while no fpu attribute is there.
<!-- FlowPowerUser · Number Count -->
<script defer src="https://cdn.jsdelivr.net/npm/@flowpoweruser/attributes@1/number-count.js"></script>Mark the number
Write the number into the Designer the way it should read in the end — with separators, with the percent sign, with everything that belongs to it. Then set the attribute on it. The target value is read from exactly that text; a separate value is only needed if it should differ from the text.
On the numbertext only inside
Text Block fpu-count-element="number"
Content: 1,250 +Publish and check
Publish the page and scroll far enough for the number to come into view. Two things have to hold: it starts counting once visible, and in the end exactly the Designer text is back — the same separators, the same decimals, the same prefix and suffix. If it stays put, fpu-count-logging="true" helps: it writes one line to the browser console on start.
OptionalStart several numbers staggeredDefault: 150 ms offset
When several metrics sit side by side, they should rarely all jump at once. Set the attribute on the surrounding container: it starts all counters inside it staggered as soon as it becomes visible, and passes its options down to its members — whatever you set on the container applies to every number within.
On the container
150Offset in milliseconds between two counters of the group. Belongs on the group element.OptionalValues, duration and repeatingDefault: 0 → text, 2000 ms
Without any setting the number counts from 0 to the value in the text, taking two seconds. These four attributes set each part of that individually. If the start value is greater than the target value, the number counts down instead of up.
On the number or the group
on the group it applies to allvalue from the textTarget value override, in case it should not come from the text content. Period as the decimal separator.0Start value. If it is above the target value, it counts down.2000Animation duration in milliseconds.falseCounts again on every viewport entry. Without this attribute the animation runs exactly once.OptionalThousands separator and consoleOtherwise as in the text
The thousands separator normally comes from the original text. If it should be a different one, this attribute sets it — either as a keyword or literally as the character to appear.
On the number or the group
as in the original textThousands separator override. “none” does not group at all; “space”, “nbsp”, “thin” and “apostrophe” are keywords. Any other value is taken literally as the separator.falseWrites one line to the browser console on start.OptionalDefaults for the whole projectthe attribute always wins
If every number in the project should share the same duration or separator, that can be set centrally once instead of writing it onto every element. An attribute on the element always beats this value.
<script>
window.fpuNumberCountConfig = {
duration: 2000,
stagger: 150,
repeat: false,
separator: ",",
logging: false,
};
</script>Additional features
- Anyone asking for less motion gets none
- With “reduce motion” set in the operating system there is no counting: the number sits at its final value right away. The content is therefore fully readable from the first second.
- Tall elements are measured differently
- The threshold sits at 40 percent of the element. If an element is taller than 40 percent of the viewport that could never be reached — there the visible portion decides instead, so large number blocks start reliably too.
- CMS counters loaded later come along
- The solution listens for the “fpu:items-changed” event and initialises newly rendered counters afterwards. Numbers that only appear through filtering, sorting or loading more therefore count just the same.
- Your own code can hook in
- The running counters, a fresh start and the cleanup are reachable from outside: instances, init and destroyAll.
- The element should contain text only
- During the animation the content is replaced via textContent. Child elements inside the number are lost in the process — prefix and suffix therefore belong in the same text content, not in separate spans.