Scroll Fade Stack — Free GSAP ScrollTrigger Crossfade Slides
Scroll Fade Stack · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Scroll Fade Stack — Crossfading Slides in a Pinned Frame

Scroll fade stack is the effect where a single fixed frame holds a series of messages and crossfades from one to the next as you scroll, with each slide drifting in the direction you're moving — a clean, focused way to present a sequence without a horizontal rail or card stack. This snippet builds it with GSAP and ScrollTrigger (from a CDN), plus plain HTML and CSS.
One frame, stacked slides
All slides are absolutely positioned in the same frame, stacked on top of each other, with only the active one at opacity: 1. Because they share the exact position, switching between them is a pure crossfade — there's no layout shift, and the headline stays anchored in the center of the screen the whole time. The frame is a fixed size so the pinned section never jumps as content changes.
Scroll progress drives the index
A pinned ScrollTrigger runs for one screen per slide (end: '+=400%' for four), and its onUpdate floors self.progress × slideCount to pick the active slide. So scrolling through the pinned section steps cleanly from slide to slide, and a progress bar at the bottom fills with the continuous progress value — the same dual continuous/discrete reading used by step sliders.
Directional crossfade
The go(i) function isn't a plain opacity swap: it checks whether you're moving forward or backward and animates the outgoing slide out in that direction while the incoming slide arrives from the opposite side (a small y offset). This directional motion communicates which way you're navigating — scroll down and slides rise; scroll up and they fall — so the sequence feels coherent rather than a random dissolve.
Guarded against thrash
onUpdate fires every frame, so go returns early when the index hasn't changed, and every tween uses overwrite: true so a fast scroll that skips a slide cancels any in-flight fade and lands cleanly on the current one. Without these guards, quick scrolling would stack half-finished opacity tweens and flicker.
Composited and smooth
The crossfades animate only opacity and a tiny y transform, both GPU-friendly, with will-change hints. The pin is the only structural change, and it's handled by ScrollTrigger, so the effect stays smooth and fully reversible — scroll back up and the slides crossfade in reverse.
Customizing it
Add slides (bump the end accordingly), change the drift distance or fade duration, swap text for media, or add a snap so the scroll settles on whole slides. Pair it with scroll pin steps, a scroll sticky stack, or a testimonial slider.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to trace the index math and guard conditions by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how flooring self.progress times the slide count picks a stable index, and why the go function's early-return-on-same-index check and each tween's overwrite: true are both necessary to prevent flicker on fast scrolling. The same assistant can help optimize it — asking whether the onUpdate callback, which runs every scroll frame, could be made cheaper for a stack with dozens of slides, or whether the directional y-offset math could be simplified. It's also great for extending the effect: ask it to add keyboard or dot navigation that jumps to a specific slide and updates the scroll position to match, snap the scroll to whole slides at rest, or swap the text slides for image or video content. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "scroll fade stack" effect in plain HTML, CSS, and JavaScript using GSAP and its ScrollTrigger plugin (load both from a CDN, no build step).
Requirements:
- A fixed-size frame containing several slides, all absolutely positioned in the exact same spot (stacked on top of each other), with only one slide at a time visible via opacity, so switching slides never shifts layout.
- A single pinned, scrubbed ScrollTrigger on the frame's container, whose scroll distance is proportional to the slide count (e.g. one full viewport height of scroll per slide).
- In that trigger's onUpdate callback, compute the target slide index by flooring self.progress multiplied by the slide count (clamped to the last valid index), and only trigger a slide change if that index actually differs from the currently active one — do not re-run the crossfade every frame.
- When the active index changes, determine the scroll direction (whether the new index is greater or less than the current one) and animate the outgoing slide's opacity to 0 while translating it up or down depending on that direction, while simultaneously animating the incoming slide in from the opposite vertical offset to opacity 1 — so the motion visibly communicates forward vs. backward navigation, not just a plain cross-dissolve.
- Every one of these crossfade tweens must use overwrite: true so that a fast scroll skipping past several slides cancels any in-flight tween instead of stacking multiple competing animations on the same element.
- Update a progress bar element's width continuously (every onUpdate call, not just on index change) from self.progress times 100%, so the bar gives continuous feedback even though the slide switching itself is discrete.
- Confirm the whole thing reverses correctly on scroll-up using the same onUpdate logic, with no separate reverse-specific code path.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Add the GSAP CDNsInclude gsap and ScrollTrigger from the CDN panel.
- 2Paste HTML, CSS, and JSA pinned frame shows the first slide.
- 3Scroll downSlides crossfade upward one to the next.
- 4Watch the barA progress bar fills across the section.
- 5Scroll back upSlides crossfade downward in reverse.
- 6Add a slideAdd a .fs-slide and extend the end value.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
All slides are absolutely positioned in the same fixed-size frame, stacked on top of each other, with only the active one at full opacity. Because they share the exact position, switching is a pure crossfade with no layout shift, and the centered headline stays anchored the whole time the section is pinned.
A pinned ScrollTrigger runs for one screen per slide, and its onUpdate floors self.progress times the slide count to pick the index. A progress bar fills from the same continuous progress value, so the section steps cleanly between slides while the bar shows exact position — the dual discrete and continuous reading.
The go function checks whether you are scrolling forward or backward and animates the outgoing slide out in that direction while the incoming slide arrives from the opposite side with a small y offset. So scrolling down makes slides rise and scrolling up makes them fall, communicating navigation direction instead of a random dissolve.
onUpdate fires every frame, so go returns early when the index has not changed, and every tween uses overwrite: true so a fast scroll that skips slides cancels any in-flight fade and lands cleanly on the current one. Without these guards, quick scrolling would stack half-finished opacity tweens and flicker.
Keep the active index and progress in state. In a mount effect, register ScrollTrigger and create the pinned trigger whose onUpdate sets the index and progress; render the active slide and bar width from state. Return a cleanup that reverts the GSAP context so the pin is removed on unmount. The crossfade CSS ports unchanged.