← /lab

// live demo

Scroll-Driven

Parallax, reveals, and scrub progress with zero JavaScript driving the animation. The browser is the engine: animation-timeline: scroll() ties the progress bar and parallax layers to the page, animation-timeline: view() with animation-range reveals each panel as it enters, and @property interpolates the custom props that morph the pinned scene. Scroll is the only input.

Heads up: this browser doesn’t support CSS Scroll-Driven Animations yet. Everything below is laid out and fully readable — it just won’t move. Try a recent Chrome, Edge, or Firefox to see scroll drive the whole page.

// view() timeline

Each panel animates off its own position in the viewport.

No IntersectionObserver. No scroll listener. Every card below carries its own view() timeline and an animation-range like entry 0% cover 40% — the platform reveals it as it crosses the fold and freezes it there.

01

Slide & fade

Translate + opacity keyed to entry. It rises into place as it enters.

02

Scale up

A @property-registered scale interpolates from 0.82 to 1 across the entry range.

03

Skew in

Rotate + skew unwinding to flat. Pure declarative keyframes, no easing library.

04

Stagger

Same effect, later in the grid — so it triggers later, naturally staggered by layout.

05

Cover range

Reveals over cover instead of entry — tied to time on screen.

06

Exit too

This one also dims toward exit 100% — scroll past and it settles back.

// lateral travel

A whole product rail scrubs sideways.

The section supplies one named view timeline. Its track travels from right to left as the section crosses the viewport, creating a native horizontal showcase without wheel hijacking or a JavaScript carousel.

// staggered ranges

One timeline. Five deliberate entrances.

Scroll becomes the editing timeline.

Each word shares the section timeline but owns a later animation-range. The result is directed typography rather than six cards politely fading at once.

// clip-path comparison

Scroll reveals the transformed system.

A scrubbed clip-path uncovers the finished interface while its divider crosses the frame at exactly the same rate.

before Static surface One layer · one state · no spatial cue
after Directed surface Depth · timing · narrative progression

// perspective stack

Cards separate into depth as they arrive.

Independent view timelines lift each layer out of a compressed stack. Scroll backward and the assembly closes again.

// scroll() scrub

Pinned. Scrubbed by scroll alone.

This panel sticks while you scroll a tall track. A single scroll(root block) timeline drives a registered --p custom property from 0 to 1, and that one value morphs the shape’s rotation, scale, border-radius, and hue. Rewind by scrolling up — it’s the timeline, not a play-once.

// why it matters

Off the main thread, no jank, no library.

Scroll-driven animations run in the browser’s compositor, not in a requestAnimationFrame loop you have to budget. That means no scroll-listener layout thrash, no GSAP, no ScrollTrigger — and it degrades to a clean static layout where the API isn’t present.