For each digit position, build a vertical "reel" strip listing 0 through 9, and show it through a window exactly one digit tall with overflow: hidden. When the value changes, translateY the whole reel so the target digit slides into that window — since every digit is spaced identically, the distance is simply (target digit − previous digit) × one digit's height.
Store the current digit in a CSS variable (--d) and drive the transform with a percentage — translateY(calc(var(--d) * -10%)) — so the reel lands on the correct digit regardless of the actual pixel height, even with a responsive clamp() size. A touch of ease-out on the transition lets the reel settle smoothly instead of clunking to a stop.
Rolling from 9 back to 0 (say 19 → 20) can look like it's spinning backward through nine digits. Duplicating 0–9 twice in the strip and always computing the target index as "forward only" keeps the roll moving in one direction.
When to use
Use it for stats and scoreboards where a counter visibly climbs or drops. With many digits (six or more) or very frequent updates, the reels end up in constant, dizzying motion — throttle the update rate, or fall back to a plain count-up.