Scroll Timeline Beam — Free HTML CSS JS Snippet

Scroll Timeline Beam · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

Read-line anchored
Reveals as content reaches your eye level.
Scroll-filled beam
Beam height tracks how far you have read.
Glowing progress
A gradient beam with a soft shadow.
Sequential dot lighting
Each dot lights as it passes the line.
Card reveal
Milestones brighten and slide into place.
Fully reversible
Scrolling up deactivates milestones.
rAF-throttled scroll
Passive, batched reads and writes.
Layout-driven
Adapts to any number of milestones.

About this UI Snippet

Scroll Timeline Beam — A Glowing Line That Fills as You Scroll

Screenshot of the Scroll Timeline Beam snippet rendered live

The scroll timeline beam is the storytelling layout where a vertical line runs down a list of milestones and a glowing beam progressively fills it as you scroll — lighting each dot and revealing its card the moment you reach it. This snippet builds the whole scroll-linked timeline with plain HTML, CSS, and a small vanilla JavaScript handler.

A read line drives everything

The effect is anchored to an imaginary horizontal "read line" partway down the viewport — here at 55% of the window height. As you scroll, content passes this line, and two things key off it: how far the beam has filled, and which milestones have been activated. Tying the animation to a fixed line on screen (rather than to absolute scroll position) makes it feel like the timeline reveals itself exactly as it reaches your eye level.

Filling the beam

A gray track sits behind the milestones, and a gradient .tb-beam overlays it. Each frame, update() computes how much of the timeline lies above the read line — mid - rect.top, clamped between 0 and the timeline's height — and sets that as the beam's height. So the glowing beam grows downward from the top of the timeline to wherever you've read to, with a soft box-shadow making it luminous. Because only one property (height) changes, the fill is cheap and smooth.

Lighting milestones as you pass

For each item, the handler measures its dot's center against the read line and toggles a .lit class once the dot has passed it. That class lights the dot — filling it with color and adding a glow — and brings its card to life, transitioning the card from dimmed and offset (opacity: .35, translateX(14px)) to full and in place. So milestones activate in sequence as the beam reaches them, and because it's a class toggle, scrolling back up deactivates them again — the timeline is fully reversible.

Efficient scroll handling

The scroll listener is { passive: true } and throttled with requestAnimationFrame behind a ticking flag, so the measurements and writes run at most once per frame. A resize listener re-runs the update because the read line is a fraction of viewport height. All layout reads (getBoundingClientRect) happen together before the writes, avoiding layout thrashing.

Reads from the DOM, not hard-coded

The script measures the live timeline and each dot's actual position, so adding, removing, or resizing milestones requires no code changes — the beam length and the activation points all derive from the rendered layout. The milestones are simple cards with a year, heading, and description.

Customizing it

Move the read line by changing the 0.55 factor to trigger earlier or later, recolor the beam gradient and dot glow, adjust the card's dimmed start state, or change the spacing. Add as many milestones as you like. Pair it with stacking scroll cards or a container scroll reveal for a complete scroll-driven story page.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to work through the read-line math or the rAF throttling by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the beam's fill is computed from mid minus rect.top rather than from window.scrollY directly, and why every getBoundingClientRect read happens before any style write inside update. The same assistant can help optimize it — for example checking whether the per-item forEach loop that toggles lit classes could be replaced with a single IntersectionObserver per milestone to cut down on layout reads for timelines with dozens of entries. It is just as useful for extending the effect: ask it to add a subtly different read-line position for mobile viewports, animate the dot with a small pulse the instant it lights, or drive an accompanying progress percentage label alongside the beam. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "scroll timeline beam" in plain HTML, CSS, and vanilla JavaScript only — no animation library, no ScrollTrigger, no build step.

Requirements:
- A vertical timeline of milestone items, each with a dot and a card, laid beside a thin background track running the full height of the timeline container.
- Define a fixed horizontal "read line" at a percentage of the viewport height (for example 55%). Do not use absolute scroll position for anything — every calculation must be relative to this read line and the timeline's own bounding rectangle.
- On every scroll event, compute how much of the timeline lies above the read line (clamped between 0 and the timeline's total height) and set that value as the height of a gradient beam overlay positioned on top of the background track, so the beam visually fills downward as the reader scrolls.
- For every milestone, compare its dot's vertical center (via getBoundingClientRect) to the read line and toggle a "lit" class on the milestone once the dot has passed it. The lit class must transition the dot to a filled, glowing state and the card from a dimmed, offset state to a fully visible, in-place state — and removing the class (by scrolling back up) must reverse both transitions.
- Throttle the scroll handler so the actual read/write work runs at most once per animation frame (using a ticking flag and requestAnimationFrame), and register the scroll listener as passive. Also re-run the calculation on window resize.
- All getBoundingClientRect reads for a given frame must happen before any style writes, to avoid forcing synchronous layout thrashing.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="tb-intro">Scroll to trace the timeline ↓</div>
<section class="tb-timeline" id="tbTimeline">
  <div class="tb-track"><div class="tb-beam" id="tbBeam"></div></div>
  <div class="tb-item"><span class="tb-dot"></span><div class="tb-card"><time>2021</time><h3>Founded</h3><p>Two engineers, one repo, and a very long roadmap.</p></div></div>
  <div class="tb-item"><span class="tb-dot"></span><div class="tb-card"><time>2022</time><h3>First 1,000 users</h3><p>The waitlist emptied overnight after launch.</p></div></div>
  <div class="tb-item"><span class="tb-dot"></span><div class="tb-card"><time>2023</time><h3>Series A</h3><p>Raised to grow the team and ship the API.</p></div></div>
  <div class="tb-item"><span class="tb-dot"></span><div class="tb-card"><time>2024</time><h3>Global launch</h3><p>Edge regions on five continents went live.</p></div></div>
  <div class="tb-item"><span class="tb-dot"></span><div class="tb-card"><time>2025</time><h3>One million</h3><p>Crossed a million developers building on the platform.</p></div></div>
</section>
<div class="tb-outro">The beam and dots fill as you pass them.</div>

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA vertical timeline of milestones renders with a dim track.
  2. 2
    Scroll downA glowing beam fills the line from the top as you go.
  3. 3
    Pass a milestoneIts dot lights up and the card brightens into place.
  4. 4
    Scroll back upMilestones deactivate again — the effect is reversible.
  5. 5
    Add milestonesInsert more items; the beam adapts automatically.
  6. 6
    Move the triggerChange the read-line factor to fire earlier or later.

Real-world uses

Common Use Cases

Company history
Tell an origin story before an animated gradient CTA.
Product roadmaps
A scroll take on a vertical timeline.
Onboarding journeys
Case studies
Reveal milestones over stacking scroll cards.
Process explainers
Trace steps as the reader scrolls.
Scroll-link demos
A reference for read-line progress effects.
Related: Three.js Scroll Möbius Strip Ride
See the Three.js Scroll Möbius Strip Ride for a related scroll pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It's an imaginary horizontal line at 55% of the viewport height that the animation keys off. The beam fills to wherever the read line has reached in the timeline, and milestones activate as their dots cross it. Anchoring to a fixed on-screen line makes the timeline reveal itself right as content reaches the reader's eye level, rather than at arbitrary scroll positions.

Each frame the handler computes how much of the timeline lies above the read line — the read-line position minus the timeline's top, clamped to its height — and sets that as the beam's height. The gradient beam grows downward from the top to that point, with a box-shadow making it glow. Only the height changes, so it stays cheap.

Yes. Each milestone's lit state is a class toggled by comparing its dot's center to the read line every frame. Scrolling up moves the dot back below the line, which removes the class and returns the dot and card to their dimmed state. So the whole timeline activates and deactivates as you scroll either way.

No. The script measures the live timeline height and each dot's actual position with getBoundingClientRect, so the beam length and the activation points derive from the rendered layout. Add, remove, or resize milestones and everything adjusts with no code changes.

Render the milestones from data and run the scroll/resize handler in a mount effect with cleanup, writing the beam height and toggling each item's lit state via refs or a state array. Read the read-line fraction from window height. The CSS ports directly. In Tailwind, style the track and beam with utilities and drive the height and lit class through inline styles and conditional classes.