webdevpuneet.com

Scroll Before After — Free GSAP Image Wipe Snippet

Scroll Before After · Scroll · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<section class="sba-top"><p>Scroll ↓</p></section>
<section class="sba-stage" id="sbaStage">
  <div class="sba-compare">
    <div class="sba-before">
      <div class="sba-scene sba-scene-before">
        <span class="sba-tag">Before</span>
        <div class="sba-sky"></div><div class="sba-hill"></div><div class="sba-sun"></div>
      </div>
    </div>
    <div class="sba-after" id="sbaAfter">
      <div class="sba-scene sba-scene-after">
        <span class="sba-tag">After</span>
        <div class="sba-sky"></div><div class="sba-hill"></div><div class="sba-sun"></div>
      </div>
    </div>
    <div class="sba-divider" id="sbaDivider"><span>⟷</span></div>
  </div>
  <p class="sba-hint">The wipe is driven by your scroll — not a drag handle.</p>
</section>
<section class="sba-bottom"><p>Fully wiped. Scroll up to restore the "before" view.</p></section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Scroll-driven wipe
The scrollbar sweeps the comparison divider.
No-squish clipping
Fixed-width inner scene inside a widening window.
Single proxy value
One tweened number drives window and divider.
Glowing seam
Divider with handle sits exactly on the edge.
Pinned + scrubbed
160% of scroll maps to the full reveal.
Bidirectional
Scroll up to restore the before view.
CSS placeholder scenes
Gradient scenes swap cleanly for photos.
Anchored labels
After tag reveals with its own layer.

About this UI Snippet

Scroll Before After — A Comparison Wipe Driven by the Scrollbar

Screenshot of the Scroll Before After snippet rendered live

The scroll before/after is an image-comparison slider with the drag handle removed: the page's own scrollbar sweeps the divider. As you scroll through the pinned section, the "after" scene wipes across the "before" scene from left to right, and scrolling back restores it. It's a strong pattern for redesigns, photo edits, and renovation reveals because the user controls the reveal speed with a gesture they were already making. This snippet builds it with GSAP ScrollTrigger (from a CDN) and a two-layer clipping trick.

The wipe is a widening window, not a moving image

Both scenes are absolutely stacked in the frame. The .sba-after layer starts at width: 0% with overflow: hidden, while its inner .sba-scene is locked to the full frame width. Widening the outer layer therefore *uncovers* more of a stationary scene rather than stretching it — the classic comparison-slider clipping trick. Without the fixed-width inner element, the after image would squish horizontally as the window grew, which is the number-one bug in naive implementations.

One proxy tween drives both the window and the divider

GSAP tweens a plain { p: 0 } object to 100 on a pinned, scrubbed ScrollTrigger. The onUpdate callback writes the same percentage to two places: the after layer's width and the divider's left. Sharing one number guarantees the glowing seam always sits exactly on the reveal edge — there's no second animation that could drift out of phase.

Pinned scrub makes the reveal deliberate and reversible

start: 'top top' with end: '+=160%' pins the frame for 1.6 viewport-heights of scrolling, mapping the full 0–100% wipe onto that distance. scrub: 0.3 adds just enough smoothing that wheel ticks glide rather than jump. Because scrub ties progress to scroll position rather than playing a one-shot animation, the comparison is bidirectional — users naturally rock back and forth across the seam to compare details, which is exactly how people use drag-handle sliders.

The scenes are pure CSS, ready to swap for images

Each scene is a sky gradient, a rounded hill, and a sun — deliberately simple placeholders that make the wipe legible in the demo. In production you'd replace each .sba-scene with an <img> (or background-image) at the same fixed width; the clipping math is identical. Matching camera position between the two shots is what sells the effect.

Why width instead of clip-path

Animating clip-path: inset() also works, but a width-based window keeps the divider trivial to position (same percentage) and works in every browser that runs GSAP, including ones with patchy clip-path transition support. The layers are compositor-friendly because nothing outside the frame reflows — the frame's size never changes.

Labels stay anchored to their own layers

The "Before" tag lives on the base layer's left corner and "After" pins to the right corner *inside* the clipped layer, so it only becomes visible once enough of the after scene is revealed — a free bit of progressive labeling that reinforces which side is which.

Scroll-driven beats drag-driven on touch screens

Classic comparison sliders bind a horizontal drag to the divider — which collides head-on with vertical page scrolling on touch devices: the browser has to guess whether a diagonal swipe means "scroll the page" or "move the handle," and either choice frustrates someone. Driving the wipe from vertical scroll removes the entire input layer: no pointer handlers, no touch-action negotiations, no clamping math, and the one gesture mobile users already perform controls the comparison. The handle in this snippet is purely decorative — a landmark for the seam, not a control — which is also why it needs no hit area or focus handling.

Customizing it

Swap in real screenshots or photos, flip the wipe direction by anchoring the after layer to the right, or shorten end for a snappier reveal. For a drag-controlled version see the image comparison snippet; pair this one with a scroll zoom hero opener or a scroll curtain reveal transition into the next section.

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 JSTwo stacked scenes with a zero-width after window.
  3. 3
    Scroll into the stageThe frame pins and the divider starts sweeping.
  4. 4
    Rock back and forthThe wipe is scrubbed — compare any region freely.
  5. 5
    Finish the wipeAt 100% the after scene fully replaces the before.
  6. 6
    Swap in real imagesReplace each scene with a fixed-width img or background.

Real-world uses

Common Use Cases

Redesign showcases
Wipe from an old UI to the new one with the scrollbar sweeping the divider, followed by a scroll reveal grid of details.
Photo editing demos
Show raw versus graded shots, and offer a drag-based image comparison slider for visitors who prefer to control it.
Renovation reveals
Present before and after property shots inside a scroll pin story, with a fixed-width inner scene so the content never squishes.
Performance case studies
Contrast old and optimised dashboards in performance case studies, with a scroll story chart showing the measured improvement numerically.
Section hand-offs and upgrades
Use the wipe like a scroll curtain reveal between sections, or contrast plans before a pricing card.
Related: Scroll Blur Focus
See the Scroll Blur Focus for a related scroll pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

A pinned ScrollTrigger scrubs a proxy object from p: 0 to p: 100 across 160% of scroll distance. Its onUpdate writes that one percentage to both the after layer's width and the divider's left offset, so the seam always sits exactly on the reveal edge and sweeping is fully reversible.

The after layer is only a clipping window — overflow: hidden with an animated width — while the scene inside it is locked to the full frame width. Widening the window uncovers more of a stationary image instead of resizing it. Skipping that fixed-width inner element is the classic bug that makes comparison sliders stretch.

Replace each .sba-scene's contents with an img styled to the same fixed frame width (width: min(760px, 92vw); height: 100%; object-fit: cover). Keep both shots aligned — same crop and camera position — because the wipe only reads as a true comparison when features line up across the seam.

Yes. For right-to-left, anchor the after layer to the right (right: 0 instead of left) and pin its inner scene to the right edge, updating the divider from the right. For a vertical wipe, animate height with a fixed-height inner scene and move the divider's top. The single-proxy pattern stays identical.

Horizontal drag handles fight vertical page scrolling on touch — the browser must guess whether a diagonal swipe scrolls or drags, and touch-action tuning only partially fixes it. Scroll-driving the wipe eliminates the conflict: the gesture users already make controls the comparison, there are zero pointer handlers to write, and the divider becomes a decorative landmark that needs no hit area, clamping, or focus management.

Build the tween in a mount effect (useEffect, onMounted, or ngAfterViewInit) with refs for the stage, after layer, and divider, and revert the gsap.context in the cleanup so the pin unregisters on unmount. Writing styles directly in onUpdate is deliberate — routing the percentage through framework state would re-render every scroll tick. Tailwind handles the frame styling cleanly.