064

Chrono Rail Timeline

£10 or the whole Vault, £99

A pinned horizontal timeline where vertical scroll drives a chronology of full dated entries past a fixed playhead, with a velocity blur, ruler era marks and a straight gliding settle.

ScrollTrigger Lenis advanced
7 more details
horizontal-scrollpinningscrubvelocitysnaptimeline3d-transforms

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

Updated

About this effect

A horizontal timeline built around an edit-suite playhead. The section pins, vertical scroll carries a rail of dated entries sideways, and a fixed head at the centre of the viewport decides what is current: the entry under the head is a full chronology record (a photograph, year, title, a lead paragraph and a block of labelled facts), while the entries either side compress, tilt and blur into shapes.

Read the full effect overview

Every channel is driven by the same scroll input. Distance from the playhead sets each entry's scale, depth and legibility, with the lead copy and the detail block fading faster than the card itself so the rail never reads as a wall of small print. Scroll velocity blurs the ruler and splits the era name into an accent-and-ink pair; the ruler runs at a third of the rail's speed so the strip reads as depth rather than a flat band. When the wheel stops the rail snaps to the nearest entry and glides to rest with a little carried momentum instead of a flat stop.

There is no tab bar: era boundaries are printed onto the ruler at the scroll position of their first entry, so jumping to an era is a mark on the timeline rather than a widget above it. The left and right arrow keys tween the same scroll position the moment the rail is on screen, without a click or a tab into it first; Home and End do the same once focus is inside the rail.

Chrono Rail Timeline - GSAP animation effect preview

What's included

9 items
  • Pinned rail: vertical scroll drives a horizontal chronology past a fixed centre playhead
  • Full entries, not captions: photograph, year, title, a lead paragraph and a structured detail block (labelled facts or a short what-changed list) per stop
  • Distance-from-head response: entries scale, rotate in 3D, fade and blur by how far they sit from the playhead, with a steeper falloff on the body copy and detail block than on the card
  • Velocity smear: fast scroll blurs the ruler and splits the era title into an accent-and-ink pair, decaying on its own clock after the input stops
  • Gliding settle: snap to the nearest entry, a short carried-momentum glide on the rail and a bloom on the entry that landed
  • Era marks printed onto the ruler at their first entry's scroll position, placed from the same snap points the rail lands on, with Left/Right tweening the real scroll position through Lenis as soon as the rail is on screen (no click or tab first), and Home/End doing the same once focus is inside the rail
  • Parallax ruler running at a third of the rail's speed, plus an era wash that crossfades hue as the head enters a new era
  • Geometry re-measured on every ScrollTrigger refresh, with a guard that keeps the last good measurement through a mid-pin refresh
  • Accessible: reduced motion renders the whole chronology as a static grid, a polite live region announces the entry under the head, focus inside the rail brings its entry under the head, and the render loop is gated on an IntersectionObserver

Perfect for

5 use cases
  • Studio, brand and company history sections that need more than a stacked list
  • Product roadmaps and release chronologies on a landing page
  • Case study or process walkthroughs where the order of steps is the story
  • Festival, exhibition and programme line-ups arranged by date
  • Editorial retrospectives and anniversary features

How it works

3 sections

ScrollTrigger pins the section and reports progress and speed; nothing is rendered there. A single per-frame callback reads those two values and writes the frame: progress maps onto the rail's position, each entry's distance from the viewport centre drives its transform in one write, and the speed-derived fringe, flare and blur go into CSS custom properties. The same distance is published as a --chrono-t custom property, which is what fades the lead paragraph and the detail block ahead of the card.

Entry centres come from layout offsets, never from transforms, so scaling an entry can never feed back into the maths. Snap points are those same centres normalised to 0-1; the era marks are positioned from those snap points, and the settle runs on a separate inner wrapper so its glide never fights the scrubbed position.

Era jumps tween the document scroll (through Lenis when it is running) rather than the rail, which keeps the pinned trigger authoritative.

Plugins ScrollTrigger
Difficulty Advanced
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid effect

Purchase to unlock the code.

Buying Chrono Rail Timeline opens 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.

£10 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £99, one payment.

Documentation

Quick Start

1. Add to your HTML <head>:

Code snippet omitted: it ships with the download.

2. Add before the closing </body> tag:

Code snippet omitted: it ships with the download.

3. Add the effect HTML anywhere in your <body>:

Code snippet omitted: it ships with the download.

Swap chrono__facts for chrono__changes (a plain <ul>) on entries where a short "what changed" list reads better than labelled facts. Both carry the same distance-from-head fade.

Using It With Your Own Design

What the effect needs from your markup. Only the data- hooks, never the class names. [data-chrono] is the pinned section; inside it [data-chrono-track] is the element the rail transform is written to and [data-chrono-inner] is the flex row of entries that the settle glide moves. Every [data-chrono-entry] must be a direct child of the inner row, laid out horizontally and in chronological order. Everything else is optional and simply not driven if absent: [data-chrono-card] (the child that blooms on settle — put it inside the entry, not on the entry itself), [data-chrono-readout], [data-chrono-era-title], [data-chrono-band], [data-chrono-ruler], [data-chrono-live] (a polite live region announcing the entry under the head), and [data-chrono-jump="<era>"] buttons. The jump buttons are positioned by the script at the scroll position of their era's first entry, and their shared parent gets an is-placed class once that has happened, so style the un-placed state as a plain row: that is what a visitor with no JS gets.

Per-entry data: data-chrono-stamp is written into the readout, data-chrono-era pairs the entry with a jump button, data-chrono-era-name fills the era title, data-chrono-hue (0-360) sets the era wash hue.

What is only the demo's styling. The grey ground, the paper card with its hairline border, Mona Sans with tabular figures for the dates, the hot pink on the playhead and the live entry, the photographs in chrono__mark (baked into assets/img/), the small arrow caps on the playhead, and the ruler's ink hairlines. Restyle all of it freely; none of it is read by the script. Only --chrono-fringe, --chrono-flare and --chrono-blur on the section, and --chrono-t on each entry, are written by the script, so keep whatever you want to respond to velocity or distance reading those.

CSS the effect depends on:

  • [data-chrono-inner] must be display: flex; width: max-content with transform-style: preserve-3d, and its parent chain must set min-width: 0 or the max-content rail stretches the whole page sideways.
  • Put overflow: hidden on the element above the one carrying perspective, not on the perspective element itself. Clipping on a perspective ancestor clips its 3D descendants in perspective space, and an unfiltered entry sitting at z: 0 — the one under the head — vanishes from the raster while every measurement still says it is on screen.
  • The section is height: 100svh; the pin distance is scroll length, not section height. The shipped card fills the deck's height (the viewport stretches, the inner row is height: 100%, and the photograph row is minmax(0, 1fr)), because the product frame is short and wide and a content-height card floats in it. On phones the card sizes to its content instead, or the photograph becomes a poster.
  • Every entry publishes its distance from the head as --chrono-t (0 at the head, 1 at the edge of the focus range). Fade your body copy and any detail block off that value — the shipped CSS uses calc(1 - var(--chrono-t, 0) * 1.15) on the lead and * 1.35 on the detail block — or a rail of full-length entries reads as a wall of unreadable small print. Anything you fade this way must be readable at --chrono-t: 0, which is the state without JS.
  • The entries carry no .has-js pre-hide: nothing here is a reveal, so without JS the rail renders as a plain horizontal row of readable cards.
  • Restore a static layout under prefers-reduced-motion (the shipped CSS turns the rail into a grid). The script adds chrono--static in that branch, so style that class too if your markup differs.

Options

Set on the [data-chrono] element.

Attribute Values Default Description
data-chrono-pace Any number (px) 500 Scroll distance per entry. Lower is a faster chronology; with full-length entries, below ~350 they pass faster than they can be read.
data-chrono-focus Any number 1.02 How far either side of the playhead an entry still reads as in focus, as a multiple of the entry pitch. Below ~0.9 neighbours pop rather than compress.
data-chrono-smear 0 to ~2 1 Peak ruler blur and colour fringe on a hard flick. 0 disables the smear entirely.
data-chrono-snap true, false true Snap to the nearest entry, and with it the gliding settle.

Accessibility

  • Reduced motion: prefers-reduced-motion: reduce skips the pin entirely. The rail becomes a static grid of entries, all readable, in both CSS and the JS branch.
  • Keyboard: Left/Right move one entry and work as soon as the rail is on screen, with no click or tab first — the listener sits on the document and only claims the key while the rail owns the middle of the viewport, so the rest of the page still scrolls normally. Home and End jump to the ends but stay scoped to focus inside the rail, because taking those from a visitor who wants the top or bottom of the document costs more than it buys. The rail is still focusable, and the era marks on the ruler are real <button> elements with aria-current and a padded press target around the hairline tick. Anything focusable inside an entry brings that entry under the head, so tabbing never lands on something off screen.
  • Announcement: [data-chrono-live] is a polite live region carrying the year and title of the entry under the head. Scrolling moves focus nowhere, so without it the rail is silent to a screen reader.
  • No JS: nothing is pre-hidden. Without the script the rail is a plain horizontal row of readable cards.
  • Touch: the mechanic is unchanged on a phone — vertical scroll still drives the rail. One entry fits beside the head, so the distance-from-head compression does more of the work, and the entry type steps down rather than the content being cut.

Dependencies

  • GSAP 3.15.0
  • ScrollTrigger
  • Lenis (optional; era jumps route through it when it is present)

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