How do you animate elements on scroll?

Scroll animations reveal, move or transform content as the page scrolls. There are two approaches: IntersectionObserver in JavaScript, which adds a class when an element enters the viewport and works in every browser, and native CSS scroll-driven animations (animation-timeline: scroll() or view()), which tie an animation to the scroll position without JavaScript.

How to choose, performance and accessibility
  • Use Fade Up or Stagger Cards for sections that appear, a reading Progress Bar on long articles, Parallax or sticky scrollytelling for storytelling pages, and Vertical Snap for full-screen slides.
  • CSS scroll-driven animations run in Chrome, Edge and Safari 26 but not yet in Firefox, so keep content visible without them.
  • Animate only transform and opacity, and turn effects off for prefers-reduced-motion.
  • Fade Up, Progress Bar, Sticky Image Change and Vertical Snap are among the free effects with full code; the rest come with Premium.

Scroll Basics

20 fundamental scroll and parallax effects.

First element

Appears rising up with a fade

Second element

Progressive animation on scroll

Third element

Uses animation-timeline: view()

Fourth element

Each element animates individually

Fifth element

All CSS, zero JavaScript

Scroll ↓

Fade Up

Elements that appear rising up with a progressive fade via animation-timeline: view().

CSSScrollFade
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="sdc-scroll-container">
    <div class="sb-fadeup-content">
      <div class="sb-fadeup-box"><h4>First element</h4><p>Appears rising up with a fade</p></div>
      <div class="sb-fadeup-box"><h4>Second element</h4><p>Progressive animation on scroll</p></div>
      <div class="sb-fadeup-box"><h4>Third element</h4><p>Uses animation-timeline: view()</p></div>
      <div class="sb-fadeup-box"><h4>Fourth element</h4><p>Each element animates individually</p></div>
      <div class="sb-fadeup-box"><h4>Fifth element</h4><p>All CSS, zero JavaScript</p></div>
    </div>
  </div>
  <span class="sdc-scroll-hint">Scroll ↓</span>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.sb-fadeup-content {
  height: 900px;
  padding: 60px 20px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.sb-fadeup-box {
  padding: 24px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  text-align: center;
  opacity: 0;
  transform: translateY(60px);
  animation: sbFadeUp linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 50%;
}

.sb-fadeup-box h4 {
  font-size: 0.9rem;
  font-weight: 700;
  color: #fff;
  margin: 0 0 6px;
}

.sb-fadeup-box p {
  font-size: 0.75rem;
  color: rgba(255, 255, 255, 0.5);
  margin: 0;
}

.sdc-scroll-container {
  width: 100%;
  height: 280px;
  overflow-y: auto;
  position: relative;
  scroll-behavior: smooth;
}

.sdc-scroll-container::-webkit-scrollbar {
  width: 4px;
}

.sdc-scroll-container::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.05);
}

.sdc-scroll-container::-webkit-scrollbar-thumb {
  background: rgba(139, 92, 246, 0.4);
  border-radius: 2px;
}

.sdc-scroll-hint {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: rgba(255, 255, 255, 0.3);
  z-index: 5;
  pointer-events: none;
  animation: sdcHintPulse 2s ease-in-out infinite;
}

@keyframes sbFadeUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes sdcHintPulse {

  0%,
  100% {
    opacity: 0.3;
  }

  50% {
    opacity: 0.7;
  }
}
JavaScript
/* Pure CSS β€” no JavaScript required */

Scale In

Elements that scale up from a smaller size with animation-timeline: view().

CSSScaleScroll

Slide In

Elements sliding in from left and right alternately via view().

CSSSlideScroll

Parallax

Parallax effect where the background moves at a different speed via scroll(nearest).

CSSParallaxScroll

Progress Reveal

Text that progressively fills with an animated gradient via scroll(nearest) and @property.

CSSProgressGradient

Stagger Cards

Cards appearing in cascade with incremental animation-delay and view().

CSSStaggerCards

Horizontal Scroll

Horizontal scrolling with scroll-snap for smooth navigation between elements.

CSSHorizontalSnap

Rotate In

Cards appearing with rotation and scale via view() and animation-delay.

CSSRotateStagger

Blur Reveal

Progressive focus effect with blur(20px) animated to blur(0) via view().

CSSBlurReveal

Counter Scroll

Animated counters that increment when they enter the container's view.

JSCounterScroll

Scroll-Driven Fade

Native CSS animation driven by scroll with animation-timeline: view() and entry range.

CSSViewFade

View Timeline Scale

Elements that progressively scale up via animation-timeline: view() and cover range.

CSSScaleView

Scroll Progress Bar

Sticky progress bar linked to the container scroll via scroll(nearest).

CSSProgressSticky

Parallax Layers CSS

Layers moving at different speeds via scroll(nearest) for a depth effect.

CSSParallaxLayers

Rotate On Scroll

Element performing a 720-degree rotation linked to scroll via scroll(nearest).

CSSRotateScroll

Slide In CSS Native

Native CSS left/right slide with animation-timeline: view() and cover range.

CSSSlideNative

Blur to Clear

Transition from blur(10px) to blur(0) for a cinematic effect via view().

CSSBlurView

Stagger Reveal CSS

Item grid with incremental native CSS animation-delay and view() for a cascade effect.

CSSStaggerNative

Clip Path Reveal

Progressive reveal via clip-path: inset() animated by view() and cover range.

CSSClip-pathReveal

Color Change

Progressive color change (purple, pink, cyan, green) linked to scroll via scroll(nearest).

CSSColorScroll

Progress Bar

Scroll to see the bar fill up. Uses animation-timeline: scroll() to link the animation to the container scroll.

Scroll ↓

Progress Bar

Progress bar linked to scroll via animation-timeline: scroll(). Fills from 0% to 100% in pure CSS.

CSS Scroll Progress
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="sdc-scroll-container">
    <div class="sdc-progress-bar">
      <div class="sdc-progress-fill"></div>
    </div>
    <div class="sdc-progress-content">
      <h3>Progress Bar</h3>
      <p>Scroll to see the bar fill up. Uses animation-timeline: scroll() to link the animation to the container scroll.</p>
      <div class="sdc-progress-block"></div>
      <div class="sdc-progress-block"></div>
      <div class="sdc-progress-block"></div>
      <div class="sdc-progress-block"></div>
    </div>
  </div>
  <span class="sdc-scroll-hint">Scroll ↓</span>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.sdc-scroll-container {
  width: 100%;
  height: 280px;
  overflow-y: auto;
  position: relative;
  scroll-behavior: smooth;
}

.sdc-scroll-container::-webkit-scrollbar {
  width: 4px;
}

.sdc-scroll-container::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.05);
}

.sdc-scroll-container::-webkit-scrollbar-thumb {
  background: rgba(139, 92, 246, 0.4);
  border-radius: 2px;
}

.sdc-scroll-hint {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: rgba(255, 255, 255, 0.3);
  z-index: 5;
  pointer-events: none;
  animation: sdcHintPulse 2s ease-in-out infinite;
}

.sdc-progress-bar {
  position: sticky;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: rgba(255, 255, 255, 0.1);
  z-index: 10;
}

.sdc-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #8b5cf6, #ec4899);
  transform-origin: left;
  animation: sdcProgressGrow linear;
  animation-timeline: scroll(nearest);
  transform: scaleX(0);
}

.sdc-progress-content {
  padding: 20px;
  height: 800px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.sdc-progress-content h3 {
  font-size: 1rem;
  font-weight: 700;
  color: #fff;
  margin: 0;
}

.sdc-progress-content p {
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.5);
  margin: 0;
  line-height: 1.6;
}

.sdc-progress-block {
  height: 120px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

@keyframes sdcHintPulse {

  0%,
  100% {
    opacity: 0.3;
  }

  50% {
    opacity: 0.7;
  }
}

@keyframes sdcProgressGrow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}
JavaScript
/* Pure CSS β€” no JavaScript required */

Fade In Reveal

Elements that progressively appear via animation-timeline: view() and animation-range: entry.

CSS View Reveal

Parallax Layers

Parallax layers with different speeds, driven entirely by scroll() in native CSS.

CSS Parallax Layers

Scale on Scroll

Cards that scale from 70% to 100% when entering the view via animation-timeline: view().

CSS Scale View

Color Shift

Background gradient that evolves through 5 color palettes while scrolling, via animation-timeline: scroll().

CSS Gradient Scroll

Text Reveal

Words that slide up from the bottom and progressively reveal while scrolling with view().

CSS Text Reveal

Rotate on Scroll

Geometric shape with 720-degree rotation linked to scroll. Borders rotate in the opposite direction.

CSS Rotate Scroll

Slide In Cards

Cards that alternately slide in from left and right with view() and animation-range: entry.

CSS Slide View

Zoom & Blur

Circle that zooms in and unblurs at the scroll center, then continues zooming and reblurs on exit.

CSS Zoom Blur

Horizontal Scroll

Vertical scroll converted into horizontal card scrolling via animation-timeline: scroll().

CSS Horizontal Scroll

Chapter 1: Introduction

Welcome to this demonstration of a scroll-linked progress bar. The bar at the top of this container progresses based on your scroll position.

This effect uses the CSS property animation-timeline: scroll() which links a @keyframes animation directly to the container's scroll position.

Chapter 2: How It Works

The scroll-timeline property creates a named timeline on the scrollable container. The progress-bar animation then uses this timeline to animate the transform: scaleX() property.

The result is a smooth progress bar that accurately represents the percentage of content read, without any JavaScript.

Chapter 3: Applications

Ideal for blog posts, documentation pages, tutorials, or any long-form content where the user wants to visualize their reading progress.

Compatible with modern browsers supporting Scroll-Driven Animations (Chrome 115+, Edge 115+).

Chapter 4: Conclusion

Scroll-linked animations open new possibilities for user experience, all in pure CSS with no impact on JavaScript performance.

Scroll to see the effect

Progress Bar

Reading progress bar driven by animation-timeline: scroll(), zero JavaScript.

CSS scroll() Progress
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="progress-scroll-container">
    <div class="slr-progress-bar-track">
      <div class="slr-progress-bar-fill"></div>
    </div>
    <div class="progress-content">
      <h3>Chapter 1: Introduction</h3>
      <p>Welcome to this demonstration of a scroll-linked progress bar. The bar at the top of this container progresses based on your scroll position.</p>
      <p>This effect uses the CSS property animation-timeline: scroll() which links a @keyframes animation directly to the container's scroll position.</p>
      <h3>Chapter 2: How It Works</h3>
      <p>The scroll-timeline property creates a named timeline on the scrollable container. The progress-bar animation then uses this timeline to animate the transform: scaleX() property.</p>
      <p>The result is a smooth progress bar that accurately represents the percentage of content read, without any JavaScript.</p>
      <h3>Chapter 3: Applications</h3>
      <p>Ideal for blog posts, documentation pages, tutorials, or any long-form content where the user wants to visualize their reading progress.</p>
      <p>Compatible with modern browsers supporting Scroll-Driven Animations (Chrome 115+, Edge 115+).</p>
      <h3>Chapter 4: Conclusion</h3>
      <p>Scroll-linked animations open new possibilities for user experience, all in pure CSS with no impact on JavaScript performance.</p>
    </div>
  </div>
  <span class="slr-scroll-hint">Scroll to see the effect</span>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.slr-scroll-hint {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.7rem;
  color: rgba(255, 255, 255, 0.4);
  pointer-events: none;
  z-index: 10;
  animation: hintBounce 2s ease-in-out infinite;
}

.progress-scroll-container {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  position: relative;
  scroll-timeline: --progressScroll y;
}

.slr-progress-bar-track {
  position: sticky;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: rgba(255, 255, 255, 0.1);
  z-index: 5;
}

.slr-progress-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, #6366f1, #d946ef, #f59e0b);
  transform-origin: left;
  transform: scaleX(0);
  animation: progressGrow linear both;
  animation-timeline: --progressScroll;
}

.progress-content {
  padding: 16px 12px;
}

.progress-content p {
  font-size: 0.8rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.7);
  margin-bottom: 12px;
}

.progress-content h3 {
  font-size: 0.9rem;
  color: #a78bfa;
  margin-bottom: 8px;
}

@keyframes hintBounce {

  0%,
  100% {
    transform: translateX(-50%) translateY(0);
  }

  50% {
    transform: translateX(-50%) translateY(-4px);
  }
}

@keyframes progressGrow {
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
}
JavaScript
/* Pure CSS β€” no JavaScript required */

Horizontal Scroll Section

Section scrolling horizontally during vertical scroll, pure CSS with scroll-timeline.

CSS scroll-timeline Horizontal

Sticky Reveal Stack

Cards stacking and revealing one by one on scroll with view() timeline.

CSS view() Stack

Text Line Reveal

Each word progressively colorizes while scrolling via animation-range and view().

CSS animation-range Text

Zoom Into Content

Content starting zoomed out that scales to 100% on scroll, cinematic opening effect.

CSS view() Zoom

Rotation on Scroll

Element rotating proportionally to scroll position, dial/compass effect.

CSS scroll() Rotation

Path Drawing on Scroll

SVG path drawing itself via stroke-dashoffset animated by scroll, pure CSS.

CSS SVG scroll()

Parallax Layers

Multi-layer parallax entirely controlled by scroll() timeline, no JS.

CSS scroll() Parallax

Introduction

Scrollytelling is a narrative technique that uses page scrolling to guide the user through an immersive visual story.

Chapter 1

Scroll-triggered animations progressively reveal content, creating a natural reading rhythm that captivates attention.

Chapter 2

By combining parallax, transitions, and visual effects, each section becomes a scene in a larger narrative, transforming a simple website into a storytelling experience.

Chapter 3

The best scrollytelling examples balance aesthetics and functionality, guiding the user without overwhelming them with excessive effects.

Conclusion

Master these techniques to create memorable web experiences that tell your story with elegance and fluidity.

Scroll to explore

Progress Bar

Horizontal progress bar that fills proportionally to content scrolling.

CSS JavaScript UX
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="st-progress-scroll">
    <div class="scroll-container" id="progress-scroll">
      <div class="st-progress-bar-track">
        <div class="st-progress-bar-fill" id="progress-fill"></div>
      </div>
      <div class="progress-inner">
        <h3>Introduction</h3>
        <p>Scrollytelling is a narrative technique that uses page scrolling to guide the user through an immersive visual story.</p>
        <div class="section-divider"></div>
        <h3>Chapter 1</h3>
        <p>Scroll-triggered animations progressively reveal content, creating a natural reading rhythm that captivates attention.</p>
        <div class="section-divider"></div>
        <h3>Chapter 2</h3>
        <p>By combining parallax, transitions, and visual effects, each section becomes a scene in a larger narrative, transforming a simple website into a storytelling experience.</p>
        <div class="section-divider"></div>
        <h3>Chapter 3</h3>
        <p>The best scrollytelling examples balance aesthetics and functionality, guiding the user without overwhelming them with excessive effects.</p>
        <div class="section-divider"></div>
        <h3>Conclusion</h3>
        <p>Master these techniques to create memorable web experiences that tell your story with elegance and fluidity.</p>
      </div>
    </div>
    <span class="scroll-hint-label">Scroll to explore</span>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.st-progress-scroll {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-height: 100%;
  background: #0a0a0f;
  color: #fff;
  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.scroll-container {
  width: 100%;
  height: 280px;
  overflow: hidden auto;
  position: relative;
  scroll-behavior: smooth;
}

.scroll-container::-webkit-scrollbar {
  width: 4px;
}

.scroll-container::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.03);
}

.scroll-container::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.15);
  border-radius: 4px;
}

.scroll-container::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.3);
}

.scroll-hint-label {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: rgba(255, 255, 255, 0.3);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  pointer-events: none;
  z-index: 10;
  animation: pulseHint 2s ease-in-out infinite;
}

.st-progress-bar-track {
  position: sticky;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: rgba(255, 255, 255, 0.08);
  z-index: 20;
}

.st-progress-bar-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, #6366f1, #a855f7, #ec4899);
  border-radius: 0 2px 2px 0;
  transition: width 0.05s linear;
}

.progress-inner {
  padding: 24px 20px;
}

.progress-inner h3 {
  font-size: 1rem;
  font-weight: 700;
  color: #fff;
  margin: 0 0 12px;
}

.progress-inner p {
  font-size: 0.8rem;
  color: rgba(255, 255, 255, 0.5);
  line-height: 1.7;
  margin: 0 0 16px;
}

.progress-inner .section-divider {
  width: 60px;
  height: 2px;
  background: linear-gradient(90deg, #6366f1, transparent);
  margin: 20px 0;
}

@keyframes pulseHint {

  0%,
  100% {
    opacity: 0.3;
  }

  50% {
    opacity: 0.7;
  }
}
JavaScript
(function() {
  /* fx-0575 β€” Introduction (Progress Bar) */
  (function() {
    const progressScroll = document.getElementById('progress-scroll');
    const progressFill = document.getElementById('progress-fill');

    if (progressScroll && progressFill) {
      progressScroll.addEventListener('scroll', () => {
        const scrollTop = progressScroll.scrollTop;
        const scrollHeight = progressScroll.scrollHeight - progressScroll.clientHeight;
        const progress = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0;
        progressFill.style.width = progress + '%';
      });
    }
  })();
})();

Parallax Layers

Three visual layers move at different speeds to create an immersive depth effect.

CSS JavaScript Parallax

Reveal on Scroll

Elements appear with a fade and vertical slide when they enter the visible area.

IntersectionObserver CSS Animation

Sticky Horizontal

Automatic horizontal scrolling simulating panel navigation, ideal for presenting steps or projects.

CSS Animation Auto-play

Text Highlight on Scroll

Each word progressively lights up as you scroll, creating a guided reading effect.

JavaScript CSS Typography

Zoom on Scroll

An element progressively grows with scrolling, going from a small object to a full-screen view.

JavaScript CSS Transform Scroll

Number Counter Scroll

Counters animate from zero to their target value proportionally to the scroll position.

JavaScript Animation Data

Timeline Scroll

Interactive vertical timeline with a line that progressively draws and elements that appear on scroll.

JavaScript CSS Timeline

Image Sequence

Frame-by-frame animation simulation driven by scroll, similar to Apple product pages.

JavaScript CSS Sequence

Scroll Snap Sections

Full-height sections with magnetic snapping and side pagination indicator.

CSS Scroll Snap JavaScript Navigation

Sticky Scroll

8 advanced sticky scroll and parallax effects.

01

First Section

The image stays fixed while content scrolls. Ideal technique for presenting features.

Second Section

Each text section triggers a visual change in the sticky image.

Third Section

Smooth transitions guide the user through content seamlessly.

Fourth Section

A pattern used in interactive journalism and product websites.

Scroll ↓

Sticky Image Change

Sticky image that changes while content scrolls with panels revealed via view().

CSSStickyScroll
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="sdc-scroll-container">
    <div class="ss-imgchange-content">
      <div class="ss-imgchange-display">01</div>
      <div class="ss-imgchange-panels">
        <div class="ss-imgchange-panel"><h4>First Section</h4><p>The image stays fixed while content scrolls. Ideal technique for presenting features.</p></div>
        <div class="ss-imgchange-panel"><h4>Second Section</h4><p>Each text section triggers a visual change in the sticky image.</p></div>
        <div class="ss-imgchange-panel"><h4>Third Section</h4><p>Smooth transitions guide the user through content seamlessly.</p></div>
        <div class="ss-imgchange-panel"><h4>Fourth Section</h4><p>A pattern used in interactive journalism and product websites.</p></div>
      </div>
    </div>
  </div>
  <span class="sdc-scroll-hint">Scroll ↓</span>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.ss-imgchange-content {
  height: 1200px;
  padding: 20px 12px;
}

.ss-imgchange-display {
  width: 100%;
  height: 120px;
  border-radius: 14px;
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  font-weight: 800;
  color: white;
  position: sticky;
  top: 10px;
  z-index: 2;
  animation: ssImgChange linear;
  animation-timeline: scroll(nearest);
}

.ss-imgchange-panels {
  padding-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ss-imgchange-panel {
  padding: 16px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  opacity: 0;
  transform: translateY(20px);
  animation: sbFadeUp linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 50%;
}

.ss-imgchange-panel h4 {
  font-size: 0.85rem;
  font-weight: 700;
  color: #fff;
  margin: 0 0 6px;
}

.ss-imgchange-panel p {
  font-size: 0.72rem;
  color: rgba(255, 255, 255, 0.5);
  margin: 0;
  line-height: 1.5;
}

.sdc-scroll-container {
  width: 100%;
  height: 280px;
  overflow-y: auto;
  position: relative;
  scroll-behavior: smooth;
}

.sdc-scroll-container::-webkit-scrollbar {
  width: 4px;
}

.sdc-scroll-container::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.05);
}

.sdc-scroll-container::-webkit-scrollbar-thumb {
  background: rgba(139, 92, 246, 0.4);
  border-radius: 2px;
}

.sdc-scroll-hint {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: rgba(255, 255, 255, 0.3);
  z-index: 5;
  pointer-events: none;
  animation: sdcHintPulse 2s ease-in-out infinite;
}

@keyframes sbFadeUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes ssImgChange {

  0%,
  20% {
    background: linear-gradient(135deg, #6366f1, #8b5cf6);
    box-shadow: 0 8px 30px rgba(99, 102, 241, 0.3);
  }

  25%,
  45% {
    background: linear-gradient(135deg, #8b5cf6, #d946ef);
    box-shadow: 0 8px 30px rgba(139, 92, 246, 0.3);
  }

  50%,
  70% {
    background: linear-gradient(135deg, #d946ef, #f43f5e);
    box-shadow: 0 8px 30px rgba(217, 70, 239, 0.3);
  }

  75%,
  100% {
    background: linear-gradient(135deg, #f43f5e, #f59e0b);
    box-shadow: 0 8px 30px rgba(244, 63, 94, 0.3);
  }
}

@keyframes sdcHintPulse {

  0%,
  100% {
    opacity: 0.3;
  }

  50% {
    opacity: 0.7;
  }
}
JavaScript
/* Pure CSS β€” no JavaScript required */

Horizontal Scroll

Horizontal scrolling section with snap to navigate between workflow steps.

CSSHorizontalSticky

Card Stack

Cards that stack with position sticky and view() animation in the container.

CSSStackSticky

Progress Reveal

Sticky progress bar with items progressively revealed via scroll(nearest) and view().

CSSProgressReveal

Split Screen Sticky

Sticky visual with rotating SVG and content revealed by scroll via view() and scroll(nearest).

CSSSplitSticky

Zoom In Sticky

Element that progressively zooms from scale(1) to scale(2.2) via scroll(nearest).

CSSZoomScroll

Text Highlight Scroll

Words colored sequentially on scroll with JS scoped to the container.

JSTextHighlight

Layered Parallax

5 layers moving at different speeds via scroll(nearest) for a complete parallax effect.

CSSParallaxLayers

Scroll Snap

6 scroll snap effects for precise navigation.

Section 1

Scroll to move to the next one

Section 2

Scroll stops automatically

Section 3

Smooth and controlled experience

Section 4

Last section

Scroll ↓

Vertical Snap

Sections automatically snapping to start via scroll-snap-type: y mandatory.

CSSSnapVertical
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="sn-vsnap-wrap">
    <div class="sn-vsnap-container">
      <div class="sn-vsnap-section"><h4>Section 1</h4><p>Scroll to move to the next one</p></div>
      <div class="sn-vsnap-section"><h4>Section 2</h4><p>Scroll stops automatically</p></div>
      <div class="sn-vsnap-section"><h4>Section 3</h4><p>Smooth and controlled experience</p></div>
      <div class="sn-vsnap-section"><h4>Section 4</h4><p>Last section</p></div>
    </div>
    <span class="sdc-scroll-hint">Scroll ↓</span>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.sn-vsnap-wrap {
  position: relative;
  width: 100%;
  background: #1e1b4b;
  color: #fff;
  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.sn-vsnap-container {
  width: 100%;
  height: 280px;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
}

.sn-vsnap-section {
  box-sizing: border-box;
  height: 280px;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 20px;
  text-align: center;
}

.sn-vsnap-section:nth-child(1) {
  background: linear-gradient(135deg, #1e1b4b, #312e81);
}

.sn-vsnap-section:nth-child(2) {
  background: linear-gradient(135deg, #312e81, #4c1d95);
}

.sn-vsnap-section:nth-child(3) {
  background: linear-gradient(135deg, #4c1d95, #6d28d9);
}

.sn-vsnap-section:nth-child(4) {
  background: linear-gradient(135deg, #6d28d9, #7c3aed);
}

.sn-vsnap-section h4 {
  font-size: 0.9rem;
  font-weight: 700;
  color: white;
  margin: 0;
}

.sn-vsnap-section p {
  font-size: 0.72rem;
  color: rgba(255, 255, 255, 0.6);
  margin: 0;
}

.sdc-scroll-hint {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: rgba(255, 255, 255, 0.3);
  z-index: 5;
  pointer-events: none;
  animation: sdcHintPulse 2s ease-in-out infinite;
}

@keyframes sdcHintPulse {

  0%,
  100% {
    opacity: 0.3;
  }

  50% {
    opacity: 0.7;
  }
}
JavaScript
/* Pure CSS β€” no JavaScript required */

Horizontal Snap

Horizontal scrolling with automatic snapping via scroll-snap-type: x mandatory.

CSSSnapHorizontal

Mixed Content Snap

Sections of different sizes with scroll-snap-type: y proximity for flexibility.

CSSMixedSnap

Snap + Progress

Vertical snap with progress bar and indicator dots via JS scoped to the container.

JSSnapProgress

Smooth Snap

Snap with animations triggered by IntersectionObserver scoped to the container.

JSSnapSmooth

Chapter 1 β€” The concept

Scroll inside this area: the glowing bar on the left fills at the exact pace of your reading, with a halo that reacts to position. The further you go, the higher the beam rises.

Chapter 2 β€” The marker

The luminous cursor slides along the vertical axis. It's an elegant and discreet progress marker, perfect for a long article, documentation page, or a narrative that unfolds on scroll.

Chapter 3 β€” The mechanics

No external dependency: a simple scroll listener calculates the scrolled / total height ratio and updates a CSS variable. Lightweight, smooth, and fully accessible.

Chapter 4 β€” Performance

The listener is passive and only touches a custom property, so zero costly reflow. Rendering stays at 60 fps even on mobile, and the bar follows the finger to the pixel.

Chapter 5 β€” Accessibility

The bar is purely decorative (aria-hidden): content remains fully readable and keyboard-navigable. In reduced-motion mode, instant scrolling replaces smooth-scroll.

Chapter 6 β€” The end

At the very bottom, the bar is full at 100% and the percentage confirms it. A clear marker from start to finish, without ever stealing attention from the content.

Scroll Progress Beam

A glowing lateral bar that fills with scroll progress, reactive halo and sliding cursor. Premium, discreet reading marker.

πŸ’‘ Perfect for: article progress bar, long page / docs, multi-section onboarding, storytelling landing.

Scroll CSS Variables Vanilla JS Free
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="spb-scene">
    <div class="spb-beam" aria-hidden="true"><div class="spb-beam-fill"></div><div class="spb-beam-dot"></div></div>
    <div class="spb-pct" aria-hidden="true">0%</div>
    <div class="spb-scroll">
      <div class="spb-block">
        <h4>Chapter 1 β€” The concept</h4>
        <p>Scroll inside this area: the glowing bar on the left fills at the exact pace of your reading, with a halo that reacts to position. The further you go, the higher the beam rises.</p>
        <h4>Chapter 2 β€” The marker</h4>
        <p>The luminous cursor slides along the vertical axis. It's an elegant and discreet progress marker, perfect for a long article, documentation page, or a narrative that unfolds on scroll.</p>
        <h4>Chapter 3 β€” The mechanics</h4>
        <p>No external dependency: a simple scroll listener calculates the scrolled / total height ratio and updates a CSS variable. Lightweight, smooth, and fully accessible.</p>
        <h4>Chapter 4 β€” Performance</h4>
        <p>The listener is passive and only touches a custom property, so zero costly reflow. Rendering stays at 60 fps even on mobile, and the bar follows the finger to the pixel.</p>
        <h4>Chapter 5 β€” Accessibility</h4>
        <p>The bar is purely decorative (aria-hidden): content remains fully readable and keyboard-navigable. In reduced-motion mode, instant scrolling replaces smooth-scroll.</p>
        <h4>Chapter 6 β€” The end</h4>
        <p>At the very bottom, the bar is full at 100% and the percentage confirms it. A clear marker from start to finish, without ever stealing attention from the content.</p>
      </div>
    </div>
  </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.spb-scene {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  max-height: 340px;
  border-radius: 14px;
  overflow: hidden;
  isolation: isolate;
  background: #0c1022;
  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.spb-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  scroll-behavior: smooth;
  padding: 22px 22px 22px 40px;
  box-sizing: border-box;
}

.spb-scroll::-webkit-scrollbar {
  width: 0;
}

.spb-scroll {
  scrollbar-width: none;
}

@media (prefers-reduced-motion: reduce) {
  .spb-scroll {
    scroll-behavior: auto;
  }
}

.spb-block {
  color: #e7e9f5;
}

.spb-block h4 {
  margin: 0 0 8px;
  font-size: 16px;
}

.spb-block p {
  margin: 0 0 22px;
  font-size: 13px;
  line-height: 1.7;
  color: #aeb2c8;
}

.spb-beam {
  position: absolute;
  left: 14px;
  top: 18px;
  bottom: 18px;
  width: 4px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.10);
  z-index: 3;
  pointer-events: none;
}

.spb-beam-fill {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: calc(var(--spb-p, 0) * 100%);
  border-radius: 4px;
  background: linear-gradient(180deg, #5cffd8, #6d8bff);
  box-shadow: 0 0 12px rgba(109, 139, 255, 0.8), 0 0 4px rgba(92, 255, 216, 0.9);
}

.spb-beam-dot {
  position: absolute;
  left: 50%;
  top: calc(var(--spb-p, 0) * 100%);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: #fff;
  box-shadow: 0 0 14px 3px rgba(109, 139, 255, 0.9);
}

.spb-pct {
  position: absolute;
  right: 14px;
  top: 12px;
  z-index: 3;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: #aeb2c8;
  background: rgba(0, 0, 0, 0.4);
  padding: 3px 9px;
  border-radius: 999px;
  pointer-events: none;
}
JavaScript
(function() {
  /* fx-0793 β€” Scroll Progress Beam (IIFE scopΓ©e) */
  (function() {
    document.querySelectorAll('.spb-scene').forEach(function(scene) {
      var scroller = scene.querySelector('.spb-scroll');
      var beam = scene.querySelector('.spb-beam');
      var pct = scene.querySelector('.spb-pct');
      if (!scroller || !beam) return;

      function update() {
        var max = scroller.scrollHeight - scroller.clientHeight;
        var p = max > 0 ? scroller.scrollTop / max : 0;
        beam.style.setProperty('--spb-p', p.toFixed(4));
        if (pct) pct.textContent = Math.round(p * 100) + '%';
      }
      scroller.addEventListener('scroll', update, {
        passive: true
      });
      window.addEventListener('resize', update);
      update();
    });
  })();
})();
Scroll here ↓

SVG Blinds Mask

Blinds open slat by slat at the exact pace of your scrolling, revealing the scene. Each slat is phased by its index β€” direct scroll β†’ CSS mapping, no forced animation.

πŸ’‘ Perfect for: revealing a visual hero, a product image, a portfolio section on scroll.

Scroll Mask Reveal
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="sbm-scene">
      <div class="sbm-scroll" id="sbmScroll" tabindex="0" role="region" aria-label="Scrollable demo area">
        <div class="sbm-track" style="height:340%"></div>
      </div>
      <div class="sbm-stage" aria-hidden="true">
        <div class="sbm-photo">
          <div class="sbm-caption">REVEALED</div>
        </div>
        <div class="sbm-slats">
        <span class="sbm-slat" style="--k:0"></span>
        <span class="sbm-slat" style="--k:1"></span>
        <span class="sbm-slat" style="--k:2"></span>
        <span class="sbm-slat" style="--k:3"></span>
        <span class="sbm-slat" style="--k:4"></span>
        <span class="sbm-slat" style="--k:5"></span>
        <span class="sbm-slat" style="--k:6"></span>
        <span class="sbm-slat" style="--k:7"></span>
        </div>
      </div>
      <span class="sbm-hint">Scroll here ↓</span>
    </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.sbm-scene {
  position: relative;
  width: 100%;
  max-width: 430px;
  height: 260px;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, .08);
  background: #0d0d16;
  --p: 0
}

.sbm-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  z-index: 3;
  scrollbar-width: thin
}

.sbm-stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none
}

.sbm-hint {
  position: absolute;
  right: 10px;
  bottom: 8px;
  z-index: 2;
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #8f8fb0;
  opacity: calc(1 - var(--p) * 3);
  pointer-events: none
}

.sbm-scroll:focus-visible {
  outline: 2px solid #a78bfa;
  outline-offset: -2px
}

.sbm-track {
  width: 100%
}

.sbm-photo {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: radial-gradient(circle at 30% 25%, rgba(34, 255, 170, .25), transparent 55%), linear-gradient(160deg, #143a5e, #7c3aed 70%, #db2777)
}

.sbm-caption {
  font-size: 1.3rem;
  font-weight: 800;
  letter-spacing: .3em;
  color: #fff;
  text-shadow: 0 2px 12px rgba(0, 0, 0, .6);
  opacity: clamp(0, (var(--p) - .75) * 4, 1)
}

.sbm-slats {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column
}

.sbm-slat {
  flex: 1;
  background: #0d0d16;
  transform: scaleY(clamp(0, 1 - (var(--p) * 9 - var(--k)), 1));
  transform-origin: 50% 50%
}
JavaScript
(function() {
  (function() {
    /* fx-0778 β€” SVG Blinds Mask : progression de scroll β†’ variable CSS --p (rAF, passif) */
    var sc = document.getElementById('sbmScroll');
    if (!sc) return;
    var host = sc.closest('.sbm-scene');
    var raf = null;

    function update() {
      raf = null;
      var max = sc.scrollHeight - sc.clientHeight;
      var p = max > 0 ? sc.scrollTop / max : 0;
      host.style.setProperty('--p', p.toFixed(4));
    }
    sc.addEventListener('scroll', function() {
      if (!raf) raf = requestAnimationFrame(update);
    }, {
      passive: true
    });
    update();
  })();
})();
Scroll here ↓

Corner Morph Overlay

A circle grows from the corner and swallows the screen as you scroll, revealing the next section β€” a scroll-driven chapter transition, pure clip-path.

πŸ’‘ Perfect for: section transitions, chaptered storytelling, narrative landing pages.

Scroll clip-path Transition
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="cmo-scene">
      <div class="cmo-scroll" id="cmoScroll" tabindex="0" role="region" aria-label="Scrollable demo area">
        <div class="cmo-track" style="height:340%"></div>
      </div>
      <div class="cmo-stage" aria-hidden="true">
        <div class="cmo-a"><h4>Chapter I</h4><p>Keep scrolling…</p></div>
        <div class="cmo-b"><h4>Chapter II</h4><p>…the corner swallowed it all.</p></div>
      </div>
      <span class="cmo-hint">Scroll here ↓</span>
    </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.cmo-scene {
  position: relative;
  width: 100%;
  max-width: 430px;
  height: 260px;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, .08);
  background: #0d0d16;
  --p: 0
}

.cmo-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  z-index: 3;
  scrollbar-width: thin
}

.cmo-stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none
}

.cmo-hint {
  position: absolute;
  right: 10px;
  bottom: 8px;
  z-index: 2;
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #8f8fb0;
  opacity: calc(1 - var(--p) * 3);
  pointer-events: none
}

.cmo-scroll:focus-visible {
  outline: 2px solid #a78bfa;
  outline-offset: -2px
}

.cmo-track {
  width: 100%
}

.cmo-a,
.cmo-b {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  gap: 4px
}

.cmo-a {
  background: linear-gradient(160deg, #16162a, #101020);
  color: #cfcfe8
}

.cmo-b {
  background: radial-gradient(circle at 88% 88%, rgba(255, 209, 102, .25), transparent 50%), linear-gradient(200deg, #2b1440, #7c3aed);
  color: #fff;
  clip-path: circle(calc(6% + var(--p) * 140%) at 90% 88%)
}

.cmo-a h4,
.cmo-b h4 {
  margin: 0;
  font-size: 1.05rem;
  letter-spacing: .18em;
  text-transform: uppercase
}

.cmo-a p,
.cmo-b p {
  margin: 0;
  font-size: .72rem;
  opacity: .75
}
JavaScript
(function() {
  (function() {
    /* fx-0779 β€” Corner Morph Overlay : progression de scroll β†’ variable CSS --p (rAF, passif) */
    var sc = document.getElementById('cmoScroll');
    if (!sc) return;
    var host = sc.closest('.cmo-scene');
    var raf = null;

    function update() {
      raf = null;
      var max = sc.scrollHeight - sc.clientHeight;
      var p = max > 0 ? sc.scrollTop / max : 0;
      host.style.setProperty('--p', p.toFixed(4));
    }
    sc.addEventListener('scroll', function() {
      if (!raf) raf = requestAnimationFrame(update);
    }, {
      passive: true
    });
    update();
  })();
})();
Scroll here ↓

Corner Morph Cards

Three scattered cards with organic corners straighten, align and soften their angles as you scroll β€” the assembly is pure border-radius and transform.

πŸ’‘ Perfect for: product grid intros, portfolios, β€œour services” sections.

Scroll border-radius Cards
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="cmc-scene">
      <div class="cmc-scroll" id="cmcScroll" tabindex="0" role="region" aria-label="Scrollable demo area">
        <div class="cmc-track" style="height:340%"></div>
      </div>
      <div class="cmc-stage" aria-hidden="true">
        <div class="cmc-row">
          <div class="cmc-card" style="--r:-9deg; --g:linear-gradient(145deg,#22ffaa,#0e7490)">01</div>
          <div class="cmc-card" style="--r:5deg;  --g:linear-gradient(145deg,#7c3aed,#db2777)">02</div>
          <div class="cmc-card" style="--r:11deg; --g:linear-gradient(145deg,#ffd166,#f4a300)">03</div>
        </div>
      </div>
      <span class="cmc-hint">Scroll here ↓</span>
    </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.cmc-scene {
  position: relative;
  width: 100%;
  max-width: 430px;
  height: 260px;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, .08);
  background: #0d0d16;
  --p: 0
}

.cmc-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  z-index: 3;
  scrollbar-width: thin
}

.cmc-stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none
}

.cmc-hint {
  position: absolute;
  right: 10px;
  bottom: 8px;
  z-index: 2;
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #8f8fb0;
  opacity: calc(1 - var(--p) * 3);
  pointer-events: none
}

.cmc-scroll:focus-visible {
  outline: 2px solid #a78bfa;
  outline-offset: -2px
}

.cmc-track {
  width: 100%
}

.cmc-row {
  position: absolute;
  inset: 0;
  display: flex;
  gap: calc(34px - var(--p) * 22px);
  align-items: center;
  justify-content: center
}

.cmc-card {
  width: 86px;
  height: 116px;
  display: grid;
  place-items: center;
  background: var(--g);
  color: rgba(255, 255, 255, .9);
  font-weight: 800;
  font-size: 1.1rem;
  border-radius: calc(50px - var(--p) * 40px) calc(14px + var(--p) * 0px) calc(46px - var(--p) * 36px) calc(18px - var(--p) * 8px);
  transform: rotate(calc(var(--r) * (1 - var(--p)))) translateY(calc((1 - var(--p)) * 12px));
  box-shadow: 0 10px 24px rgba(0, 0, 0, .45)
}
JavaScript
(function() {
  (function() {
    /* fx-0780 β€” Corner Morph Cards : progression de scroll β†’ variable CSS --p (rAF, passif) */
    var sc = document.getElementById('cmcScroll');
    if (!sc) return;
    var host = sc.closest('.cmc-scene');
    var raf = null;

    function update() {
      raf = null;
      var max = sc.scrollHeight - sc.clientHeight;
      var p = max > 0 ? sc.scrollTop / max : 0;
      host.style.setProperty('--p', p.toFixed(4));
    }
    sc.addEventListener('scroll', function() {
      if (!raf) raf = requestAnimationFrame(update);
    }, {
      passive: true
    });
    update();
  })();
})();
Scroll here ↓

Concentric Rings Morph

Five concentric rings draw themselves, rotate and shift hue (per-index OKLCH) at the exact pace of your scroll, around a live progress percentage.

πŸ’‘ Perfect for: reading progress, onboarding steps, immersive scroll counters.

Scroll SVG OKLCH
Effect page β†’
View source code
HTML
<div class="demo-preview">
  <div class="crm-scene">
      <div class="crm-scroll" id="crmScroll" tabindex="0" role="region" aria-label="Scrollable demo area">
        <div class="crm-track" style="height:340%"></div>
      </div>
      <div class="crm-stage" aria-hidden="true">
        <svg class="crm-svg" viewBox="0 0 120 120" aria-hidden="true">
          <circle class="crm-ring" style="--i:0" cx="60" cy="60" r="14"/>
          <circle class="crm-ring" style="--i:1" cx="60" cy="60" r="25"/>
          <circle class="crm-ring" style="--i:2" cx="60" cy="60" r="36"/>
          <circle class="crm-ring" style="--i:3" cx="60" cy="60" r="47"/>
          <circle class="crm-ring" style="--i:4" cx="60" cy="60" r="58"/>
        </svg>
        <span class="crm-pct"></span>
      </div>
      <span class="crm-hint">Scroll here ↓</span>
    </div>
</div>
CSS
.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
  box-sizing: border-box
}

.demo-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 280px;
  overflow: hidden;
  position: relative;
  background: rgb(10, 10, 15);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.crm-scene {
  position: relative;
  width: 100%;
  max-width: 430px;
  height: 260px;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, .08);
  background: #0d0d16;
  --p: 0
}

.crm-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  z-index: 3;
  scrollbar-width: thin
}

.crm-stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none
}

.crm-hint {
  position: absolute;
  right: 10px;
  bottom: 8px;
  z-index: 2;
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #8f8fb0;
  opacity: calc(1 - var(--p) * 3);
  pointer-events: none
}

.crm-scroll:focus-visible {
  outline: 2px solid #a78bfa;
  outline-offset: -2px
}

.crm-track {
  width: 100%
}

.crm-svg {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 200px;
  height: 200px
}

.crm-ring {
  fill: none;
  stroke-width: 5;
  stroke-linecap: round;
  stroke: oklch(75% .26 calc(280 + var(--i) * 26 + var(--p) * 120));
  stroke-dasharray: calc((88 + var(--i) * 69) * var(--p) * 1px) 1000px;
  transform-origin: 60px 60px;
  transform: rotate(calc(-90deg + var(--i) * 18deg * var(--p))) scale(calc(.72 + var(--p) * .28));
  opacity: calc(.35 + var(--p) * .65)
}

.crm-pct {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 1rem;
  font-weight: 800;
  color: #e7e5ff;
  letter-spacing: .08em
}

.crm-pct::after {
  content: attr(data-pct)
}
JavaScript
(function() {
  (function() {
    /* fx-0781 β€” Concentric Rings Morph : progression de scroll β†’ variable CSS --p (rAF, passif) */
    var sc = document.getElementById('crmScroll');
    if (!sc) return;
    var host = sc.closest('.crm-scene');
    var raf = null;

    function update() {
      raf = null;
      var max = sc.scrollHeight - sc.clientHeight;
      var p = max > 0 ? sc.scrollTop / max : 0;
      host.style.setProperty('--p', p.toFixed(4));
      var pct = host.querySelector('.crm-pct');
      if (pct) pct.setAttribute('data-pct', Math.round(p * 100) + '%');
    }
    sc.addEventListener('scroll', function() {
      if (!raf) raf = requestAnimationFrame(update);
    }, {
      passive: true
    });
    update();
  })();
})();

Scroll Velocity Feedback

Elements react to scroll speed: dynamic stretching and tilt that give physical weight to scrolling, then snapping back to sharp at rest.

πŸ’‘ Perfect for: editorial gallery, project grid, immersive feed, creative "about" page.

Scroll Velocity requestAnimationFrame Premium

Pinned Image Sequence

A pinned object whose 360Β° rotation is driven by scroll, Apple-style product sequence. Here, a real 3D model (.glb, self-hosted, loaded on demand) rotates as you scroll.

πŸ’‘ Perfect for: 360Β° product view, feature/teardown reveal, launch page, step-by-step visual tutorial.

Scroll Sticky CSS 3D Premium

Sticky Horizontal Reveal

A pinned section whose content scrolls horizontally as you scroll vertically. Tells a progression (process, timeline, steps) in a cinematic way.

πŸ’‘ Perfect for: process/step presentation, timeline, portfolio showcase, narrative product page.

Scroll Sticky Horizontal Premium

Scroll Choreography Sequence

Multi-element orchestration: headline, text, badges and CTA enter in cascade on the scroll timeline, like a scene being built. Each element has its own progress window.

πŸ’‘ Perfect for: assembling hero section, feature presentation, brand storytelling, "manifesto" section.

Scroll Timeline Stagger Premium

Frequently Asked Questions

How to trigger a scroll animation in JavaScript? +

Use IntersectionObserver: create an observer with a threshold (e.g., 0.1), observe your elements, and add a CSS class when they enter the viewport. It's the most performant method β€” no scroll listener needed.

How to create a CSS parallax effect? +

The simplest method uses background-attachment: fixed on a background-image. For more controlled parallax, use transform: translateY() with a reduced coefficient based on scroll position (via JS or CSS scroll-timeline).

What is scroll-timeline in CSS? +

scroll-timeline is a native CSS API that links an animation to the scroll position β€” no JavaScript needed. You set animation-timeline: scroll() and the animation progresses with the scroll. Supported by Chrome and Edge since 2024.

How to make a reading progress bar? +

Listen to the scroll event, calculate the percentage: (scrollY / (document.height - window.height)) * 100, and apply it as the width of a bar fixed at the top. Or use CSS scroll-timeline for zero JavaScript.

Do scroll animations impact performance? +

With IntersectionObserver: no, it's asynchronous. With a scroll listener: potentially yes β€” use passive: true and throttle/debounce. With CSS scroll-timeline: no, it's handled natively by the browser.

Similar Categories