Scroll Pin Steps — Free GSAP ScrollTrigger Steps Snippet

Scroll Pin Steps · Scroll · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Pinned panel
One section presents all steps.
Progress-to-step
floor(progress × count) picks the step.
Data-driven steps
Content lives in a STEPS array.
Change-guarded swaps
Only updates when the step changes.
Crossfade visuals
Active visual fades in per step.
Text pop-in
Staggered rise-and-fade per change.
Scrubbed bar
Fill width tracks continuous progress.
Synced readings
One progress drives bar and steps.

About this UI Snippet

Scroll Pin Steps — A Pinned Panel That Advances as You Scroll

Screenshot of the Scroll Pin Steps snippet rendered live

Scroll pin steps is the "how it works" pattern where a two-column panel sticks to the screen and its content — step number, heading, body, and visual — swaps from one step to the next as you scroll, with a progress bar tracking how far through you are. This snippet builds it with GSAP and ScrollTrigger (from a CDN), plus plain HTML and CSS.

Pin once, scroll through many steps

A single ScrollTrigger pins the panel with pin: true and runs for end: '+=300%' — three extra screens of scroll for four steps. During that pinned window, onUpdate reads self.progress (0 to 1) and maps it to a step index with Math.floor(progress × stepCount). So one pinned section presents the entire sequence without repeating markup for each step — the content is data, swapped in place.

Data-driven content

The steps live in a STEPS array of { num, head, body }. A show(i) function writes the active step's text into the panel and crossfades to the matching visual. Guarding with if (i === current) return means the swap only runs when the step actually changes, not on every scroll frame — important because onUpdate fires constantly while pinned.

Crossfading visuals and popping text

When the step changes, the visuals crossfade via opacity tweens (only the active one is shown), and the text elements pop in with a small fromTo rise-and-fade plus a stagger, using overwrite: true so rapid scrolling can't stack half-finished tweens. This gives each step a clean micro-transition instead of a hard text replacement.

The progress bar

Because onUpdate also has the continuous self.progress, the snippet sets a fill bar's width to progress × 100% every frame — a smooth, scrubbed indicator of position within the pinned section that's independent of the discrete step swaps. It gives the viewer a sense of "how much is left" while the steps tick over.

Discrete steps from a continuous scroll

The interesting part is mixing two readings of the same scroll: a continuous one for the progress bar, and a quantized one (floor) for which step is active. ScrollTrigger's single progress value drives both, so they stay perfectly in sync — the bar is exactly one-quarter full as step two begins, and so on.

Customizing it

Add steps to the array (and matching visuals) and bump the end accordingly, change the per-step transitions, swap emoji for real images or components, or flip the columns. Pair it with a scroll horizontal pin, a progress wizard, or scroll sticky stack.

Step by step

How to Use

  1. 1
    Add the GSAP CDNsInclude gsap and ScrollTrigger from the CDN panel.
  2. 2
    Paste HTML, CSS, and JSA two-column panel renders with step one.
  3. 3
    Scroll downThe panel pins and steps advance in place.
  4. 4
    Watch the barThe progress fill tracks your position.
  5. 5
    Scroll back upSteps reverse — everything is scrubbed.
  6. 6
    Add a stepExtend the STEPS array and the end value.

Real-world uses

Common Use Cases

How it works
Pair with a progress wizard.
Onboarding
Explain steps before an onboarding tour.
Product tours
Feature flows
Showcase beside feature cards.
Stacking
Combine with scroll sticky stack.
Pipelines
Visualize stages of a status dashboard.

Got questions?

Frequently Asked Questions

A single ScrollTrigger pins the panel and runs for end: +=300%, three extra screens for four steps. In onUpdate it reads self.progress from 0 to 1 and maps it to a step index with floor(progress times stepCount). So the whole sequence plays inside one pinned section, with the content swapped in place rather than duplicated per step.

onUpdate fires on every scroll frame while pinned, but the step only changes occasionally. The if (i === current) return guard ensures the text write, visual crossfade, and pop-in tweens run only when the active step actually changes, avoiding redundant work and preventing the entrance animation from restarting every frame.

Both come from the same self.progress value. The bar width is set to progress times 100% continuously, while the active step is the floored progress times step count. Because one number drives both, the bar is exactly one quarter full as the second step begins, and so on — the continuous and quantized readings never drift.

Visuals crossfade with opacity tweens so only the active one shows, and the text pops in with a fromTo rise-and-fade. Passing overwrite: true means a new step's tween cancels any in-flight one, so rapid scrolling cannot stack half-finished animations — each step lands in a clean state.

Keep the steps array in your component and, in a mount effect, register ScrollTrigger and create the pinned trigger whose onUpdate updates state (active index and progress) or writes to refs. Return a cleanup that reverts the GSAP context so the pin is removed on unmount. Render the active step from state; the CSS ports unchanged.