Scroll-driven Animations

Tie CSS animations to scroll position with animation-timeline: scroll() and view(). No scroll event listeners, no requestAnimationFrame, no IntersectionObserver.

1. Reading Progress Bar

The blue bar at the top of the page fills as you scroll down. Uses animation-timeline: scroll(root) — zero JavaScript. Bootstrap ScrollSpy would need scroll event listeners + offset calculations + class toggling for a similar indicator.

Scroll down to see the progress bar fill at the very top of the page.

2. Reveal on Scroll

Cards fade in and slide up as they enter the viewport. Uses animation-timeline: view() with animation-range: entry.

Card 1

Zero JavaScript

This card animates into view using only CSS. No IntersectionObserver, no scroll event listener, no animation library.

Card 2

Compositor Thread

Scroll-driven animations run on the compositor thread, guaranteeing 60fps even on busy pages. JS scroll handlers run on the main thread and can jank.

Card 3

Replaces ScrollSpy

Bootstrap ScrollSpy uses scroll events + offset math + class toggling (~43 KB JS). Scroll-driven animations do the same with a few CSS lines.

Card 4

animation-range

animation-range: entry 0% entry 100% means the animation plays during the element’s entry into the viewport. You can also use exit, contain, or cover.

Card 5

view() vs scroll()

scroll() tracks the scroll position of a container. view() tracks an element’s visibility within its scrolling ancestor. Both are pure CSS.

Card 6

Progressive Enhancement

All animations are wrapped in @supports (animation-timeline: scroll()). Unsupported browsers see static content — no broken layouts.

3. Parallax Header

The background moves at a different rate than the page scroll, creating a depth effect. Uses animation-timeline: view() with animation-range: entry 0% exit 100% — the shift happens as this section enters and exits the viewport.

Parallax Section

This background shifts as you scroll. Pure CSS — no JS parallax library.

4. Horizontal Scroll Indicator

Dots and progress bar below track your horizontal scroll position. Uses named view-timeline on each slide, timeline-scope to hoist them to the wrapper, and scroll-timeline for the progress bar. Zero JavaScript.

Slide 1

Scroll horizontally to see the indicator dots and progress bar update in real-time.

Slide 2

Each dot’s opacity and scale are driven by its corresponding card’s view-timeline.

Slide 3

Uses named view-timeline + timeline-scope to connect dots outside the scroller to items inside it.

Slide 4

Compare to Bootstrap Carousel indicators which need JS to sync active state.

Slide 5

Zero JavaScript. Zero IntersectionObserver. Zero scroll event listeners. Just CSS.

Implementation: Scroll-driven Animations (zero JS)
@supports (animation-timeline: scroll()) {

  /* Reading progress bar — fills as page scrolls */
  .reading-progress {
    position: fixed; top: 0; left: 0;
    width: 0%; height: 4px;
    animation: grow-width linear;
    animation-timeline: scroll(root);
  }

  /* Reveal on scroll — cards fade in on viewport entry */
  .reveal-card {
    animation: reveal-up linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }

  /* Parallax — background shifts as section scrolls through viewport */
  .parallax-inner {
    animation: parallax-shift linear;
    animation-timeline: view();
    animation-range: entry 0% exit 100%;
  }
  @keyframes parallax-shift {
    from { transform: translateY(-80px) scale(1.15); }
    to { transform: translateY(80px) scale(1.15); }
  }

  /* Horizontal scroll indicator — named timelines + timeline-scope */
  .hscroll-item:nth-child(1) { view-timeline: --slide-1 inline; }
  .hscroll-item:nth-child(2) { view-timeline: --slide-2 inline; }
  /* ... one per slide ... */

  .hscroll-wrapper {
    timeline-scope: --slide-1, --slide-2, --slide-3, --slide-4, --slide-5;
  }

  .scroll-indicator-dot:nth-child(1) { animation-timeline: --slide-1; }
  .scroll-indicator-dot:nth-child(2) { animation-timeline: --slide-2; }
  /* ... dot animation tracks its slide's visibility ... */

  /* Progress bar tracks horizontal scroll position */
  .hscroll-container { scroll-timeline: --hscroll inline; }
  .hscroll-progress {
    animation: grow-width linear;
    animation-timeline: --hscroll;
  }

}