CSS Scroll Animation — Interactive Examples & Code

Learn how to animate elements on scroll using CSS, JavaScript, and fullPage.js, with copyable, configurable code for each approach.

Fade & Slide Scroll Animation

Cards fade in and slide into view as they enter the viewport using CSS animation-timeline: view(). Configure direction, distance, and trigger range below.

Fade & Slide

This card fades in and slides into view using CSS animation-timeline: view(). Configure direction, distance, and fade above.

Scroll-Driven

The animation progress is linked to the scroll position, no JavaScript timers or observers needed.

Copy & Paste

Add data-scroll="fade-slide" to any element. Set direction, distance and fade with data attributes.

Lightweight

No dependencies or libraries. Pure CSS using modern scroll-driven animations.

Performant

Runs on the compositor thread for buttery-smooth 60fps animations.

0px120px

Animation Trigger

0%50%
50%100%

Scale Scroll Animation

Elements grow from a smaller scale into full size as they enter the viewport. Pure CSS using animation-timeline: view().

Scale Up

Element grows from a smaller size into full scale as it enters the viewport.

Zoom Into View

Configurable starting scale. Great for images, cards, and hero elements.

Pure CSS

No JavaScript needed. Uses animation-timeline: view().

Smooth Motion

Scale transitions feel natural and draw the eye to content as it appears.

Flexible

Works on any element: cards, images, headings, or entire sections.

0.10.9

Animation Trigger

0%50%
50%100%

Blur Reveal Scroll Animation

Content goes from blurry to sharp focus as it scrolls into view, using CSS filter: blur() with animation-timeline: view().

Blur Reveal

Content goes from blurry to sharp focus as it scrolls into view.

Focus Effect

Perfect for text reveals and dramatic image entrances.

Configurable

Adjust the blur amount with the slider on the right.

Eye-Catching

Blur-to-sharp transitions naturally guide the reader's attention.

GPU Accelerated

CSS filter: blur() is hardware-accelerated for smooth performance.

2px20px

Animation Trigger

0%50%
50%100%

Rotate on Scroll Animation

Elements rotate from an angle to their normal position as they enter the viewport. Configurable degrees; negative for counter-clockwise.

Rotate In

Element rotates from an angle to its normal position on scroll.

Tilt Entrance

Subtle rotation adds a dynamic feel to any element.

Adjustable

Set rotation with the slider. Negative = counter-clockwise.

Combine Effects

Pair rotation with fade or scale for richer entrance animations.

No Libraries

Zero dependencies. Just CSS properties on your HTML elements.

-45deg45deg

Animation Trigger

0%50%
50%100%

Clip-Path Wipe Reveal

Content is revealed with a directional wipe using CSS clip-path animated with animation-timeline: view(). Choose from 4 directions.

Clip-Path Wipe

Content is revealed with a directional wipe using CSS clip-path.

Curtain Reveal

Creates a clean reveal effect. Choose from 4 directions.

GPU Efficient

clip-path is hardware-accelerated for smooth animations.

Clean Reveals

Wipe transitions create elegant content reveals from any direction.

Versatile

Works beautifully on images, text blocks, and full sections alike.

Animation Trigger

0%50%
50%100%

3D Flip Scroll Animation

Perspective flip effect along the X or Y axis using CSS perspective() and rotate transforms, scroll-driven, no JS.

3D Flip

Perspective flip effect along X or Y axis. Adds depth to scroll entrances.

Perspective

Uses CSS perspective() and rotate transforms.

Configurable

Switch between X and Y axis. Adjust the flip angle.

Depth Effect

3D perspective adds a sense of depth that flat animations can't achieve.

Scroll-Linked

The flip progress follows your scroll position precisely.

15deg90deg

Animation Trigger

0%50%
50%100%

Slide-In from Sides Animation

Alternate left/right slide-in entrances. Great for timelines, feature lists, and comparison sections, with zero JavaScript.

From Left

Slides in from the left side.

From Right

Slides in from the right side.

Alternating

Great for alternating left/right layouts and list items.

Dynamic Layouts

Perfect for timelines, feature lists, and comparison sections.

Zero JS

All animations powered by CSS scroll-driven animations API.

20px150px

JavaScript Scroll Animation

Using the Intersection Observer API to detect when elements enter the viewport and trigger CSS transitions. Works in all modern browsers (97%+ global support), no CSS scroll-timeline required.

Intersection Observer

This card animates when it enters the viewport, detected by the Intersection Observer API.

Universal Support

Works in all modern browsers including Safari and Firefox, no CSS scroll-timeline needed.

Full Control

Programmatic control over thresholds, root margins, and animation classes.

fullPage.js Section Transitions

20+ CSS transform presets for section-to-section transitions: cube, stack, cover, vortex, and more. One config line replaces complex scroll code.

Loading preview...

fullPage.js adds

  • 80+ WebGL & CSS transitions
  • Touch & swipe support
  • Scroll snapping
  • Keyboard navigation
  • Anchor links & history
  • Lazy loading
  • 60fps GPU performance
Get fullPage.js →

Scroll inside the preview to see the transition between sections

How to Add Scroll Animations to Fullscreen Sections

Element animations with afterLoad

Use the fullPage.js afterLoad callback to trigger CSS animations on individual elements when a section comes into view:

new fullpage('#fullpage', {
  afterLoad: function(origin, destination) {
    // Animate elements in the new section
    destination.item.querySelectorAll('[data-anim]')
      .forEach(function(el) {
        el.classList.add('visible');
      });
  },
  onLeave: function(origin) {
    // Reset animations in the section being left
    origin.item.querySelectorAll('[data-anim]')
      .forEach(function(el) {
        el.classList.remove('visible');
      });
  }
});

Section transitions with Effects extension

For the transition between sections (how one section morphs into the next), add the Effects extension for CSS transform presets:

new fullpage('#fullpage', {
  effects: 'cube',   // 20+ presets: stack, cover, vortex, zoom...
  afterLoad: function(origin, destination) {
    // Element animations still work alongside section transitions
    destination.item.querySelectorAll('[data-anim]')
      .forEach(function(el) { el.classList.add('visible'); });
  }
});

CSS Scroll-Driven Animations Explained

The CSS Scroll-Driven Animations specification replaces the old pattern of reading window.scrollY in JavaScript and applying styles imperatively. Instead, you declare that a standard @keyframes animation should be driven by scroll position rather than elapsed time.

Where animation-duration: 1s normally runs an animation over one second of wall-clock time, animation-timeline: scroll() or animation-timeline: view() replaces that clock with a scroll timeline. The animation's progress (0% → 100%) maps directly to a scroll offset.

The spec defines two timeline types: Scroll Progress Timelines, linked to the scroll position of a container, and View Progress Timelines, linked to an element's visibility within the viewport. Most practical use cases come down to choosing between these two.

scroll() vs view(): What's the Difference?

These two timeline functions track fundamentally different things, and picking the wrong one is a common mistake.

scroll() — Scroll Progress Timeline

animation-timeline: scroll() maps animation progress to the scroll container's overall scroll position: 0% at the top, 100% at the bottom. The animated element's position in the viewport is irrelevant; only the container's scroll offset matters.

Use it for effects tied to page-level scroll: reading progress bars, sticky header transitions, background parallax.

/* Progress bar that fills as user scrolls the page */
#progress-bar {
  animation: grow-width linear both;
  animation-timeline: scroll(root block);
}

@keyframes grow-width {
  from { width: 0%; }
  to   { width: 100%; }
}

view() — View Progress Timeline

animation-timeline: view() maps animation progress to an element's position within the scrollport. Progress advances as the element moves through the visible area, from its bottom edge entering (entry) to its top edge exiting (exit).

Each element gets its own independent timeline. Combined with animation-range, you can restrict the animation to a specific phase, e.g. entry 0% entry 100% for entrance only, or cover 0% cover 100% while fully visible.

/* Card that fades in as it enters the viewport */
.card {
  animation: fade-up linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes fade-up {
  from { opacity: 0; transform: translateY(40px); }
  to   { opacity: 1; transform: translateY(0); }
}

Key Differences at a Glance

scroll() view()
Tracks Scroll container position Element's position in viewport
Best for Progress bars, sticky effects Element entrance animations
Timeline spec name Scroll Progress Timeline View Progress Timeline
Plays once or loops Reverses as you scroll back Reverses as element exits/enters

Core CSS Properties for Scroll Animation

Four CSS properties form the core of the spec.

animation-timeline

Replaces the time-based animation clock with a scroll or view timeline. Accepts scroll(), view(), or a named timeline reference. When set, animation-duration is ignored; scroll position controls progress instead.

/* Named timeline defined on a container, consumed by a child */
.container { scroll-timeline-name: --my-scroll; }
.child      { animation-timeline: --my-scroll; }

scroll-timeline

Defines a named Scroll Progress Timeline on a scroll container, so descendant elements can consume it via animation-timeline: --name. Shorthand for scroll-timeline-name and scroll-timeline-axis (default: block).

.scroll-container {
  overflow-y: scroll;
  scroll-timeline: --page-scroll block;
}

view-timeline

Defines a named View Progress Timeline on an element, referenceable by siblings or descendants. Shorthand for view-timeline-name, view-timeline-axis, and view-timeline-inset. The inset value adjusts the effective scrollport boundary, letting you trigger animations before or after the element fully enters.

.hero-image {
  view-timeline: --hero-reveal block;
}
.hero-caption {
  animation: reveal linear both;
  animation-timeline: --hero-reveal;
  animation-range: entry 50% entry 100%;
}

animation-range

Restricts which portion of the timeline drives the animation. Without it, the animation spans the full 0%–100% range. Accepts phase keywords (entry, exit, cover, contain) with optional percentage offsets.

/* Only animate while the element is entering the viewport */
.card {
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

/* Animate across the full visible window */
.sticky-bg {
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
}

entry spans from the element's leading edge entering the scrollport to the element being fully inside. cover runs from first pixel visible to last pixel visible. exit mirrors entry on the way out.

Browser Support for CSS Scroll Animations

Not yet Baseline. Chromium and Safari ship it by default; Firefox is the remaining holdout. See the full compatibility matrix on Can I Use.

Browser Version Status
Chrome 115+ Supported by default (July 2023)
Edge 115+ Supported by default
Safari 26.0+ Supported by default (Sept 2025)
Opera 101+ Supported by default
Firefox Not enabled by default
Firefox Nightly 136+ Behind flag only

Safari 26 added full support for animation-timeline, scroll(), view(), and animation-range. Since all iOS browsers use WebKit, this covers mobile Safari, Chrome on iOS, and every other iOS browser.

Firefox has an implementation in Nightly builds behind layout.css.scroll-driven-animations.enabled in about:config, but it hasn't shipped in the release channel. Mozilla's standards position is positive, and the feature is part of Interop 2026. Some sub-features like timeline-scope remain incomplete even in Nightly.

In practice, scroll-driven animations reach roughly 85–90% of global users today. Firefox users need a fallback.

Fallbacks for Older Browsers

With Firefox still lacking default support, production sites need a fallback. The cleanest approach is progressive enhancement: a static, usable layout by default, with scroll animations layered on via @supports.

Feature Detection with @supports

Test for animation-range alongside animation-timeline. Firefox Nightly recognizes animation-timeline but lacks full animation-range support, so a combined check avoids partial implementations:

/* Base: elements are visible and static */
.card {
  opacity: 1;
  transform: none;
}

/* Enhanced: scroll-driven animation only if fully supported */
@supports ((animation-timeline: view()) and (animation-range: 0% 100%)) {
  .card {
    animation: reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }
  @keyframes reveal {
    from { opacity: 0; transform: translateY(40px); }
    to   { opacity: 1; transform: translateY(0); }
  }
}

Intersection Observer as a JavaScript Fallback

For viewport-entrance effects, Intersection Observer is the standard JS alternative: asynchronous, off-main-thread, and universally supported:

// Only run fallback when native support is missing
if (!CSS.supports('animation-timeline', 'view()')) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      entry.target.classList.toggle('in-view', entry.isIntersecting);
    });
  }, { threshold: 0.3 });

  document.querySelectorAll('[data-scroll]')
    .forEach((el) => observer.observe(el));
}

Then add transition-based fallback styles that activate when the .in-view class is toggled:

/* Fallback: transition-based animation */
[data-scroll] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity .6s ease, transform .6s ease;
}
[data-scroll].in-view {
  opacity: 1;
  transform: translateY(0);
}

/* Override fallback when native scroll-driven animations are supported */
@supports ((animation-timeline: view()) and (animation-range: 0% 100%)) {
  [data-scroll] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

The scroll-timeline Polyfill

The flackr/scroll-timeline polyfill patches animation-timeline, animation-range, scroll-timeline, and view-timeline in unsupported browsers. It detects native support and skips itself when not needed. The trade-off is JS weight and no compositor-thread performance.

For most production use, @supports + Intersection Observer is lighter and more predictable. The polyfill makes more sense when you need exact spec behavior everywhere.

Accessibility and Reduced Motion

WCAG 2.3.3 (Animation from Interactions) requires that non-essential motion triggered by scrolling can be disabled. Scroll-driven animations, particularly parallax, scale, and 3D transforms, can cause vestibular discomfort.

The recommended pattern is no-motion-first: elements render in their final, static state by default, and scroll animations activate only when the user hasn't requested reduced motion.

Double-Gated Pattern

Nest animation-timeline inside both a motion preference check and a feature support check:

/* Default: static, fully visible layout */
.card {
  opacity: 1;
  transform: none;
}

/* Animation only when user allows motion AND browser supports it */
@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: 0% 100%)) {
    .card {
      animation: fade-up linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 100%;
    }
    @keyframes fade-up {
      from { opacity: 0; transform: translateY(40px); }
      to   { opacity: 1; transform: translateY(0); }
    }
  }
}

What Counts as "Motion"

WCAG defines motion animation as spatial movement or size changes. translate, scale, rotate, parallax, and 3D transforms all qualify. Opacity fades and color transitions do not; they're safe alternatives under reduced motion.

Options when reduced motion is active:

Avoid animation: none !important

A blanket * { animation: none !important } under prefers-reduced-motion: reduce prevents per-element tailoring and causes layout jumps when animations that position elements are abruptly killed. Handle reduced-motion per element instead, keeping content visible at its final state.

Try All 20+ CSS Transform Scroll Effects

Click any effect below and scroll inside the preview to see the transition between fullscreen sections. Each one is a single-line config with fullPage.js.

Loading preview...

fullPage.js adds

  • 80+ WebGL & CSS transitions
  • Touch & swipe support
  • Scroll snapping
  • Keyboard navigation
  • Anchor links & history
  • Lazy loading
  • 60fps GPU performance
Get fullPage.js →

Scroll inside the preview to see the transition between sections

See all 80+ scroll effects live (CSS transforms + WebGL cinematic) →

Slide-In Animation on Scroll

A slide-in animation moves an element into view from a chosen direction as it enters the viewport. With CSS animation-timeline: view(), each element carries its own independent timeline, with zero JavaScript needed.

From Left

Slides in from the left as it enters the viewport.

From Right

Alternating directions create dynamic, editorial layouts.

From Bottom

Classic slide-up, great for cards, features, and list items.

From Top

Slide-down entrance, ideal for navigation reveals.

Re-triggers on Scroll-Up

Scroll back up, and the animation re-plays. No JS needed.

20px150px

Uses data-scroll="fade-slide" with data-scroll-direction set to left, right, up, or down.

<div data-scroll="fade-slide"
     data-scroll-direction="left"
     data-scroll-distance="80">
  Slides in from the left
</div>

<style>
[data-scroll="fade-slide"] {
  animation: slideIn linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}
[data-scroll-direction="left"]  { --scroll-tx:  var(--scroll-distance, 80px); --scroll-ty: 0px; }
[data-scroll_direction="right"] { --scroll-tx: calc(-1 * var(--scroll-distance, 80px)); --scroll-ty: 0px; }
[data-scroll-direction="up"]    { --scroll-tx: 0px; --scroll-ty:  var(--scroll-distance, 80px); }
[data-scroll-direction="down"]  { --scroll-tx: 0px; --scroll-ty: calc(-1 * var(--scroll-distance, 80px)); }

@keyframes slideIn {
  from { opacity: 0; transform: translate(var(--scroll-tx, 0), var(--scroll-ty, 40px)); }
  to   { opacity: 1; transform: translate(0, 0); }
}
</style>

Reveal Effects on Scroll

Reveal effects uncover content like pulling back a curtain, using CSS clip-path animated with animation-timeline: view(). Each of the four directions creates a different dramatic entrance.

Left → Right Reveal

Curtain wipes from left to right using clip-path.

Top → Bottom Reveal

Like a window shade rolling down to expose the content.

Right → Left Reveal

Reveals from the right edge, great for alternating layouts.

Bottom → Top Reveal

Rising reveal, a dramatic entrance for hero content.

How it works

clip-path: inset() clips the element's visible area. Animating from inset(0 100% 0 0) to inset(0) reveals left-to-right.

Works on any element: images, text blocks, cards, or full-width banners. The masked area never shifts layout.

/* Left-to-right curtain reveal */
[data-scroll="clip-wipe"] {
  animation: clipRevealLeft linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes clipRevealLeft   { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }
@keyframes clipRevealTop    { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0); } }
@keyframes clipRevealRight  { from { clip-path: inset(0 0 0 100%); } to { clip-path: inset(0); } }
@keyframes clipRevealBottom { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0); } }

Text Reveal on Scroll

A text reveal animation shows each line sliding up into view as the user scrolls, popularised by editorial websites and portfolios. Each line uses overflow: hidden as a mask, so the text appears to emerge from behind an invisible boundary.

Scroll to reveal Beautiful Text Reveals One line at a time, pure CSS.
About Us We build digital experiences that matter.
Make it Count.

How it works

  1. Wrap each line in .text-reveal-line with overflow: hidden
  2. Wrap the text in .text-reveal-inner
  3. Animate translateY from 110% to 0
  4. Add animation-timeline: view()

The parent's overflow: hidden acts as the mask; text slides up from below the visible boundary without shifting layout.

<span class="text-reveal-line">
  <span class="text-reveal-inner">First line of text</span>
</span>
<span class="text-reveal-line">
  <span class="text-reveal-inner">Second line</span>
</span>

<style>
.text-reveal-line {
  display: block;
  overflow: hidden; /* clips the upward movement */
}
.text-reveal-inner {
  display: block;
  animation: textRevealUp linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 50%;
}
@keyframes textRevealUp {
  from { transform: translateY(110%); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}
</style>

Try the dedicated Text Reveal generator with more effects and controls:

Try the Text Reveal Generator →

Progress Bars Linked to Scroll

A reading progress bar fills as the user scrolls through an article. CSS scroll-timeline-name attaches a named scroll timeline to any overflow container, and a descendant element then references it with animation-timeline. No JavaScript, no scroll event listeners.

Introduction

The bar above tracks how far you've scrolled. It's powered by scroll-timeline-name, a named CSS timeline attached to this scroll container.

How it Works

The scroll container has scroll-timeline-name: --my-tl. The progress fill uses animation-timeline: --my-tl, linking its width animation to that container's scroll position.

Configurable Color

Pick a color on the right. The bar color is a CSS custom property: change it once, update everywhere. Works with gradients too.

Page-Wide Version

For a full-page reading bar, set scroll-timeline-name on html or body and apply animation-timeline to the bar. Identical behaviour.

Zero JavaScript

No scroll event handlers. No requestAnimationFrame. CSS drives the progress bar from 0% to 100% entirely on the compositor thread.

/* 1. Give the scroll container a named timeline */
.article {
  overflow-y: scroll;
  scroll-timeline-name: --article-tl;
  scroll-timeline-axis: block;
  position: relative;
}

/* 2. Sticky bar sits at the top of the container */
.progress-bar {
  position: sticky;
  top: 0;
  height: 4px;
  background: rgba(255,255,255,.1);
  z-index: 100;
}

/* 3. Fill animates from 0% to 100% based on scroll */
.progress-bar-fill {
  height: 100%;
  background: #3B82F6;
  animation: fillBar linear both;
  animation-timeline: --article-tl;
}
@keyframes fillBar {
  from { width: 0%; }
  to   { width: 100%; }
}

/* ── Page-wide version ── */
html { scroll-timeline-name: --page-tl; }
.page-progress { animation-timeline: --page-tl; }

Try the dedicated Progress Bar generator with color pickers and position controls:

Try the Progress Bar Generator →

Parallax Scroll Effects

Parallax makes background layers move at a different speed than foreground content, creating depth. CSS animation-timeline: scroll() makes multi-layer parallax possible without JavaScript, and each layer gets a different translateY range in its keyframes.

The key: background elements use a small translation range (e.g. translateY(-20%)) while foreground elements use a larger one (translateY(-50%)). Both animations run from 0% to 100% scroll progress; the difference in range creates the depth illusion.

/* Background layer: moves slowly */
.parallax-bg {
  animation: parallaxSlow linear both;
  animation-timeline: scroll(root block);
}
@keyframes parallaxSlow {
  from { transform: translateY(0); }
  to   { transform: translateY(-20%); }
}

/* Foreground layer: moves faster */
.parallax-fg {
  animation: parallaxFast linear both;
  animation-timeline: scroll(root block);
}
@keyframes parallaxFast {
  from { transform: translateY(0); }
  to   { transform: translateY(-50%); }
}

Try the dedicated Parallax generator with configurable speed, depth layers, and blur:

Try the Parallax Effect Generator →

Scroll Snap Animation

CSS Scroll Snap locks the viewport to predefined snap points after scrolling, eliminating awkward mid-section stops. Combine it with scroll-driven entrance animations for a polished, fullscreen feel. Toggle between mandatory (always snaps) and proximity (only snaps when close) below.

Section 1

Snaps into place. The card animates in as the section fills the viewport.

Section 2

Switch between mandatory and proximity modes to feel the difference.

Section 3

Mandatory: always snaps after any scroll. Proximity: only snaps when near a point.

Section 4

Change the card animation with the selector on the right.

Section 5

Works on vertical and horizontal scroll containers alike.

mandatory — Always snaps to the nearest point after any scroll.

proximity — Only snaps when scroll position is close to a snap point.

/* Snap container */
.page-container {
  height: 100vh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory; /* or 'y proximity' */
}

/* Each section snaps to start */
.section {
  height: 100vh;
  scroll-snap-align: start;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Animate content as section enters */
.section .card {
  animation: cardReveal linear both;
  animation-timeline: view();
  animation-range: entry 5% entry 65%;
}
@keyframes cardReveal {
  from { opacity: 0; transform: scale(0.88); }
  to   { opacity: 1; transform: scale(1); }
}

Try the dedicated Scroll Snap playground with vertical, horizontal and fullPage.js demos:

Try the Scroll Snap Playground →

Background Change on Scroll

Changing the page background as the user scrolls adds visual storytelling, and each section gets its own color theme. Using scroll-timeline-name on the container and multi-stop @keyframes, a color-shifting element tracks scroll progress in pure CSS.

Blue Theme

Deep navy. The color bar at top is blue.

Green Theme

Forest green. The bar transitions to green.

Amber Theme

Warm amber. Bar turns golden.

Purple Theme

Rich violet. Bar shifts to purple.

End State

100% scroll reached. Bar is red. All pure CSS.

How it works

  1. Set scroll-timeline-name on the scroll container
  2. Reference it from any element via animation-timeline: --name
  3. Use multi-stop @keyframes for the color sequence
  4. For the page background, put the named timeline on html

The colored bar uses 5 keyframe stops, each matching a section's accent color, driven entirely by scroll position.

/* Named timeline on the scroll container */
.page {
  overflow-y: scroll;
  scroll-timeline-name: --page-scroll;
  scroll-timeline-axis: block;
}

/* Fixed background shifts through colors as you scroll */
.page-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  animation: bgCycle linear both;
  animation-timeline: --page-scroll;
}
@keyframes bgCycle {
  0%   { background: #0d1b4d; } /* Section 1: blue   */
  25%  { background: #064e3b; } /* Section 2: green  */
  50%  { background: #451a03; } /* Section 3: amber  */
  75%  { background: #3b0764; } /* Section 4: purple */
  100% { background: #1e3a5f; } /* Section 5: navy   */
}

/* Smooth interpolation between sections */
/* CSS automatically interpolates colors between keyframe stops */

Try the dedicated Background Change generator with palette presets and live controls:

Try the Background Change Generator →

FAQ

What is CSS scroll animation?
CSS scroll animation uses animation-timeline: scroll() to link CSS keyframe animations to the scroll position instead of time. As users scroll, elements animate based on how far the page has been scrolled, enabling fade-ins, slide-ups, and scale transforms without JavaScript.
Does CSS animation-timeline work in all browsers?
As of 2025, animation-timeline: view() is supported in Chrome 115+, Edge 115+, and Safari 18+. Firefox support is behind a feature flag. For full cross-browser support, use the Intersection Observer API or a library like fullPage.js.
How do I use data attributes to configure scroll animations?
Add data-scroll="fade-slide" to any element, then configure with optional attributes: data-scroll-direction (up, down, left, right), data-scroll-distance (pixels), and data-scroll-fade-from (0 to 1). A small init script reads these and sets CSS custom properties. Copy the code from any example above to get started.
How do I animate elements when scrolling to a section?
Use the Intersection Observer API to detect when a section enters the viewport, then add a CSS class that triggers the animation. With fullPage.js, use the afterLoad callback to animate elements when a section becomes active.
What's the difference between scroll animation and scroll transition?
Scroll animation refers to animating individual elements (text, images, cards) as they enter the viewport. Scroll transition refers to the visual effect between fullscreen sections, how one section transforms into the next (cube rotation, page curl, ripple). CSS scroll-timeline handles element animations; fullPage.js handles section transitions.

Make Your Website Stand Out with Fullscreen Effects

Add scroll transitions, parallax and animations to your site with fullPage.js. One component, 80+ effects.

Brandire
New World of Work
OW Consulting

Powering fullscreen design for Google, Sony, BBC, eBay & more

Get fullPage.js
+ Suggest Effect