section-032

Odometer Reel Stats

Vault only: not sold separatelyGet the Vault

A stats section where a sideways strip of action photographs rolls one giant odometer figure, reel by reel, as visitors swipe, drag or jump between results.

advanced
11 more details
odometer-counterhorizontal-scrollsnapdragmomentumvelocity-reactiveinterruptible-transitionsprogressive-enhancementkeyboard-navigationcontainer-queriesresponsive-section

This section reads better at your own screen size than in the frame below:

Released

About this section

Odometer Reel Stats Section shows what an organisation or product has achieved, one figure at a time, and ties each figure to a photograph, a plain-English explanation and a timeframe. On a signal-orange ground, a single giant condensed figure sits beside its explanation, above a full-bleed strip of action photographs that scrolls sideways.

Read the full section overview

The strip drives the figure. As visitors swipe, drag or scroll the photographs, every digit of the figure is a reel that rolls continuously towards the next result: the units spin fastest, like a real odometer, leading digits fold away when the next figure is shorter, and a directional blur and lean follow how fast the strip is moving. Stop halfway and the reels stop halfway; reverse and they roll back exactly. A lane of split buttons below jumps to any figure by pointer, touch or keyboard.

The fictional Swiftline Run Club example reports a first season in five figures with five generated photographs of its runs. The figures are an ordinary ordered list: without JavaScript, each photograph shows with its own figure and copy in a sideways-scrolling strip, and screen readers always read the final figures from that list.

Odometer Reel Stats - website section preview

What's included

10 items
  • One giant figure whose digits are rolling reels, scrubbed continuously by the photo strip's scroll position
  • Odometer behaviour: units spin fastest, leading digits and group separators fold away, prefixes and suffixes crossfade
  • Speed-linked directional blur and lean on the figure, and a slight lean on the photographs, that settle when the strip stops
  • Native sideways scrolling with snapping for touch and trackpads, plus mouse drag with a flick that carries on to the next figure
  • Split lane of numbered buttons with a progress fill that tracks the strip exactly
  • Arrow keys, Home and End on the focused strip; repeated presses mid-move step on from the figure being approached
  • Readout of label, explanation and timeframe that changes with direction-aware motion and never shifts the layout
  • First figure rolls up from zero when the section arrives; screen readers only ever get the final values
  • Figures, counts, split labels and reel widths derived from the markup; the figure is fitted to its column
  • Reduced-motion, no-JavaScript and missing-GSAP fallbacks, independent instances and exact teardown

Perfect for

4 use cases
  • Sports clubs, events and charities reporting a season or campaign in numbers
  • Product launches showing adoption, distance, time saved or other results with the photograph behind each
  • Studios and agencies presenting results for a case study
  • Annual reviews and impact pages that need each figure explained in plain English

How it works

3 sections

Each figure is a list item with its photograph, value, label, explanation and timeframe. The script reads those values, builds one reel per digit position (each a strip of 0 to 9) and measures the digit, separator and affix widths in the chosen face so it can fit the figure to its column.

The photo strip is an ordinary horizontal scroller with CSS scroll snapping. Its scroll position is mapped to a fractional progress between the snap points of neighbouring items, and every frame each reel is positioned between its digit in the current figure and its digit in the next, with extra full turns on the lower digits. Digits and separators that exist in only one of the two figures shrink and fade in step, so the figure changes length smoothly. Because the figure is a pure function of scroll position, interruption and reversal are exact.

Buttons, keys and mouse flicks move the strip with a GSAP tween on scrollLeft, with snapping suspended during the move and restored at the end; any wheel, touch or new drag cancels a move in progress. A quickTo on the scroll velocity drives an SVG directional blur and a skew that decay to nothing when the strip stops. The section never installs a scroller or captures vertical wheel input, so the host page keeps its scroll.

Difficulty Advanced
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Vault only

Get the Vault to use this section.

The Vault includes Odometer Reel Stats, the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate. Sections are available only in the Vault for now while the collection is small.

Documentation

Quick Start

  1. Copy assets/style.css, assets/script.js and assets/img/ into your project.
  2. Load the fonts, the stylesheet, GSAP and the script:

Code snippet omitted: it ships with the download.

  1. Copy the whole <section class="ors" data-odometer-stats> element from index.html, between the EXTRACT START and EXTRACT END comments. Everything else in index.html is demo page furniture.

The script mounts every [data-odometer-stats] on the page. It needs GSAP core only; no plugins.

Make it yours

Figures

Each figure is one list item inside <ol data-ors-list>:

Code snippet omitted: it ships with the download.

  • The value is the text of data-ors-value. Use whole numbers; group separators such as 46,812 or 46.812 are kept and fold away on shorter figures.
  • A prefix or suffix goes in its own span beside the value: <span class="ors-item__prefix" data-ors-prefix>£</span> before it, or <span class="ors-item__suffix" data-ors-suffix>%</span> after it.
  • The label says what is counted; the body says why it matters; the timeframe says when. Keep them all: they are what make a figure mean something.
  • data-ors-split is the short name on the figure's button under the strip. On phones only the number shows.
  • Add, remove or reorder whole <li> elements. The reels, buttons, 01 / 05 counters and the figure's size all follow the markup. With a single item the section shows one static figure and no buttons.
  • The first two images use loading="eager"; later ones can stay lazy, because the script loads them all once the page has loaded.

Screen readers read each item's own copy, so they always hear the final figure, never a rolling one. The large rolling figure and the readout beside it are hidden from assistive technology.

Photographs

File Shows Size
assets/img/distance.webp Runners crossing a footbridge at dusk 1536 x 1024
assets/img/first-finish.webp A first-time finisher breaking the tape 1536 x 1024
assets/img/every-saturday.webp Runners in heavy rain 1536 x 1024
assets/img/came-back.webp Two runners high-fiving 1536 x 1024
assets/img/free.webp A group setting off before dawn 1536 x 1024

To replace one, put your photo in assets/img/, change the src, the width and height to its real pixel size, and the alt text. Any aspect ratio works: the frame is 16:10 on wide screens and 4:5 on phones (set by --ors-slide-ratio), and the photo is cropped to fill it. Move the crop with --ors-focus on the <figure> (for example 30% 50% keeps the left side). Around 1600px wide is plenty. Keep the main subject near the middle: the photos drift slightly sideways as the strip moves.

Brand variables

All on the section root, .ors:

Code snippet omitted: it ships with the download.

A rebrand, for example a cycling event on lime:

Code snippet omitted: it ships with the download.

Check the contrast of --ors-ink on --ors-ground (the demo pair is 6.05:1). The figure is fitted to its column from real glyph measurements, so any display face works; choose one with lining figures. The Anybody width axis is what gives the narrow figures and wide titles: with another face, remove the font-stretch and font-variation-settings lines or leave them, since faces without a width axis ignore them.

Behaviour and options

  • Strip: a native sideways scroller with snapping. Touch and trackpads scroll it directly; a mouse can drag it and flick it on to the next figure. Vertical wheel movement always scrolls the page.
  • Figure: follows the strip's exact position, so stopping halfway leaves the reels halfway and reversing rolls them back. Its blur and lean follow the strip's speed and settle to nothing.
  • Buttons and keys: the buttons under the strip jump to a figure. With the strip focused, Left and Right (or Page Up and Page Down) step, Home and End go to the ends. A new press mid-move re-targets; any wheel, touch or drag cancels a move.
  • Intro: when the section scrolls into view, the first figure rolls up from zero. Set its length in seconds with data-intro-duration="1.8" on the root.

JavaScript API:

Code snippet omitted: it ships with the download.

Instances are independent; several can share a page.

Accessibility and integration

  • The heading is an <h2>; each figure's label is an <h3>. Change the tags freely: styling uses classes.
  • The strip is a labelled, focusable region; the buttons expose the current figure with aria-current and include its value in their names, and moves by button or key are announced politely.
  • With prefers-reduced-motion: reduce, there is no intro, blur, lean or parallax, the figure changes whole, and buttons jump without animation. Changing the preference while the page is open is handled.
  • Without JavaScript, or if GSAP fails to load, the section is a sideways-scrolling strip with a visible scrollbar in which every photograph carries its own figure, label, explanation and timeframe.
  • The section is full width by design and adapts to narrow columns with container queries. It does not pin, install a smooth scroller or change the page's scroll behaviour.
  • The "Find your first run" link points at #find-a-run: replace it with your own destination.

Dependencies and credits

  • GSAP 3.15.0 core from jsDelivr, under the GSAP standard licence.
  • Anybody, Geist and Geist Mono from Google Fonts, under the SIL Open Font License.
  • The five photographs are generated concept images of fictional people, made for this section; they do not show a real club or event. Swiftline Run Club and every figure, date and claim in the demo are sample content: replace them with your own before publishing.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.