ScrollReveal Fade Timeline — Alternating Origin Snippet

ScrollReveal Fade Timeline · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

Attribute-selector routing
data-side="left"/"right" lets two .reveal() calls target each side without JS branching.
Per-side stagger scope
interval staggers each selector's own match order independently, so both sides cascade in parallel.
IntersectionObserver-driven
ScrollReveal v4 observes visibility natively — no scroll-event polling or throttling code needed.
Directional origin per axis
origin: 'left'/'right' reinforces the timeline's left-right layout instead of a generic bottom fade.
Independent dot animation
Spine dots scale in with a short delay so they visibly connect to their card.
Subtle spine reveal
The connecting line fades in on its own schedule instead of competing with entry motion.
Responsive single-column fallback
CSS collapses the alternating layout to one column with left-aligned dots on narrow screens.
Zero manual observer code
The entire effect is configuration passed to sr.reveal() — no IntersectionObserver written by hand.

About this UI Snippet

ScrollReveal Fade Timeline — Alternating Origins and IntersectionObserver, Explained

Screenshot of the ScrollReveal Fade Timeline snippet rendered live

A plain scroll-reveal list — every item fading up from the bottom — reads as a generic feed. A timeline needs the motion to reinforce the layout: entries on the left side of the spine should feel like they're arriving from the left, and entries on the right should arrive from the right. ScrollReveal.js makes this a matter of calling .reveal() twice with different selectors rather than writing any custom observer code.

Two reveal calls, one per side

js sr.reveal('.srt-item[data-side="left"]', { origin: 'left', distance: '46px', interval: 120 }); sr.reveal('.srt-item[data-side="right"]', { origin: 'right', distance: '46px', interval: 120 });

ScrollReveal's origin option sets which direction the element travels *from* — 'left' means the element starts distance pixels to the left of its resting position and slides right into place, translated with opacity. Because the HTML already marks each timeline entry with data-side="left" or data-side="right", two separate attribute selectors let each side get its own reveal configuration without any JavaScript branching — the CSS attribute selector does the routing.

What interval does versus what you might expect from "stagger"

interval: 120 staggers elements matched by that one .reveal() call, delaying each subsequent match by 120ms relative to the previous one *in that selector's own match order*. Because left and right items are revealed by two independent calls, the stagger is scoped per side — left items stagger against each other, right items stagger against each other, and both groups start their timers as they individually enter the viewport (ScrollReveal uses an IntersectionObserver under the hood, not a fixed page-load timer), so items further down the page naturally reveal later regardless of interval.

Under the hood: IntersectionObserver, not scroll events

ScrollReveal.js v4 replaced its old scroll-listener implementation with IntersectionObserver, which is why this snippet doesn't debounce or throttle anything — there's no scroll handler to throttle. Each revealed element gets its own observer entry; when it crosses the configured threshold (roughly 20% visible by default) the callback fires the CSS transition. This is dramatically cheaper than polling getBoundingClientRect() on scroll, especially with dozens of timeline entries.

The spine line and dots

The vertical .srt-line gets its own subtle reveal (opacity fade with no translation) so it doesn't compete visually with the entries, and each .srt-dot reveals with a scale animation and a short delay so the dot "pops" into place slightly after its card starts sliding in — a small sequencing detail that makes the connection between dot and card read as intentional rather than coincidental.

Reusing it

Because the reveal logic is entirely selector-driven, adding a sixth timeline entry means adding one data-side HTML block — no JS changes required. Swap the alternating pattern for a single-column layout by revealing everything with the same origin.

Build with AI

Build, Understand, Optimize, and Extend It With AI

This snippet is a good example of letting data attributes route configuration instead of writing conditional JavaScript. Paste it into an AI assistant like Claude and ask it to explain exactly how two separate sr.reveal() calls, scoped by the data-side attribute selector, produce independent stagger groups that still visually interleave correctly down the page. Then ask what would happen if reset were set to true — items further up would re-hide and re-play every time you scrolled back past them, which is worth trying live to feel the difference from the default reset: false. For extension, ask it to add a progress indicator that fills the spine line proportional to scroll position, make the active (most recently revealed) entry get a persistent highlight, or convert the two-call pattern into a single reusable function that takes a side name and returns the right origin.

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 vertical "fade timeline" using ScrollReveal.js (v4, from a CDN) in plain HTML, CSS, and JavaScript.

Requirements:
- A vertical timeline with a centered connecting spine line and 5 dated entries alternating left/right of the spine, each entry marked with a data-side="left" or data-side="right" attribute and containing a year label, a title, and a short description in a card.
- Each card has a small circular dot on the spine at its vertical position.
- Configure ScrollReveal with reset: false globally (entries reveal once and stay visible).
- Call sr.reveal() TWICE: once targeting '[data-side="left"]' with origin: 'left', and once targeting '[data-side="right"]' with origin: 'right' — so left-side cards slide in from the left and right-side cards slide in from the right, reinforcing the timeline's axis. Give both an interval (e.g. 120ms) so same-side entries stagger against each other as they individually scroll into view.
- Separately reveal the spine dots with a scale-in animation and a short delay (e.g. 200ms) so each dot visibly locks into place slightly after its card starts sliding in.
- Reveal the spine line itself with a subtle opacity-only fade, distinct from the card/dot animations.
- Make it responsive: collapse to a single left-aligned column with dots on the left edge below 560px width.
- Style it as a dark, minimal theme with a blue accent color, soft card shadows, and generous vertical spacing between entries.

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

Requires
<div class="srt-stage">
  <div class="srt-head">
    <span class="srt-tag">scrollreveal · alternating origin</span>
    <h2>Company Timeline</h2>
    <p>Scroll down — each entry fades in from alternating sides as it enters the viewport.</p>
  </div>
  <div class="srt-timeline">
    <div class="srt-line"></div>
    <div class="srt-item" data-side="left">
      <div class="srt-dot"></div>
      <div class="srt-card"><span class="srt-year">2019</span><h3>Founded</h3><p>Three engineers, one laptop, zero customers.</p></div>
    </div>
    <div class="srt-item" data-side="right">
      <div class="srt-dot"></div>
      <div class="srt-card"><span class="srt-year">2020</span><h3>First 100 Users</h3><p>Word of mouth carried us through a rough year.</p></div>
    </div>
    <div class="srt-item" data-side="left">
      <div class="srt-dot"></div>
      <div class="srt-card"><span class="srt-year">2022</span><h3>Series A</h3><p>$8M raised to build out the platform team.</p></div>
    </div>
    <div class="srt-item" data-side="right">
      <div class="srt-dot"></div>
      <div class="srt-card"><span class="srt-year">2024</span><h3>1M Requests/Day</h3><p>Crossed a million daily API calls in production.</p></div>
    </div>
    <div class="srt-item" data-side="left">
      <div class="srt-dot"></div>
      <div class="srt-card"><span class="srt-year">2026</span><h3>Global Launch</h3><p>Live in 40 countries with a fully remote team.</p></div>
    </div>
  </div>
</div>

Step by step

How to Use

  1. 1
    Add the ScrollReveal CDNInclude the scrollreveal UMD build from the CDN panel.
  2. 2
    Paste HTML, CSS, and JSA five-entry timeline renders with a central spine and alternating left/right cards.
  3. 3
    Scroll down the pageLeft-side entries slide in from the left, right-side entries from the right, staggered per side.
  4. 4
    Add more entriesDuplicate an .srt-item block with the opposite data-side value — no JS changes needed.
  5. 5
    Tune the staggerAdjust interval (currently 120ms) to speed up or slow down how quickly items on the same side cascade.
  6. 6
    Change the reveal directionSwap origin: 'left'/'right' for 'bottom' on both selectors for a simpler single-direction reveal.

Real-world uses

Common Use Cases

Company/product timelines
About pages and changelogs where chronology is the point.
Course/roadmap progress
Show completed and upcoming milestones as a learner scrolls.
Case study narratives
Walk through a project's phases with alternating supporting screenshots.
Event schedules
Conference or launch-day agendas presented as a scrolling spine.
Resume/CV pages
Career history revealed as the visitor scrolls, left/right per role.
Learning ScrollReveal selectors
A concrete example of routing reveal config via data attributes instead of JS conditionals.

Got questions?

Frequently Asked Questions

Each call configures a distinct origin direction — 'left' for the left-side selector, 'right' for the right-side one. ScrollReveal's reveal() takes one origin per call, so two selectors (routed by the existing data-side attribute) is the simplest way to give each side its own travel direction without writing conditional JS.

Separately. interval staggers matches within a single reveal() call in the order they appear in the DOM for that selector, so the five left-side items stagger against each other and the right-side items stagger against each other, each group timed relative to when its own elements individually cross the viewport threshold.

No — version 4 uses IntersectionObserver internally, which the browser fires natively when an element crosses a visibility threshold, rather than ScrollReveal manually listening to scroll events and computing getBoundingClientRect() on every frame. That's why nothing in this snippet needs debouncing.

The dot's reveal call includes delay: 200, so its scale-in animation starts 200ms after it would otherwise. Combined with the card's slide-in duration of 700ms, the dot visibly 'catches up' to and locks onto the card mid-slide, reading as a deliberate connection rather than two unrelated animations.

Add another .srt-item block in the HTML with data-side set to "left" or "right" and it is automatically picked up — the reveal() selectors target the data-side attribute generically, so no JavaScript needs to change when the timeline grows.

reset: false (the default passed at ScrollReveal({ reset: false })) means each entry reveals once and stays visible on scroll-up. Setting it true would re-hide and re-animate every entry each time it leaves and re-enters the viewport — appropriate for a hero section you want to replay, but distracting on a long timeline someone might scroll past repeatedly.