Slide & fade
Translate + opacity keyed to entry. It rises into place as it enters.
// live demo
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
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.
Translate + opacity keyed to entry. It rises into place as it enters.
A @property-registered scale interpolates from 0.82 to 1 across the entry range.
Rotate + skew unwinding to flat. Pure declarative keyframes, no easing library.
Same effect, later in the grid — so it triggers later, naturally staggered by layout.
Reveals over cover instead of entry — tied to time on screen.
This one also dims toward exit 100% — scroll past and it settles back.
// scroll() scrub
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
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.