webdevpuneet.com

Video Scrub Comparison Slider — HTML CSS JS Snippet

Video Scrub Comparison Slider · Media · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="vsc-wrap">
  <div class="vsc-frame" id="vscFrame">
    <div class="vsc-layer vsc-layer-before">
      <div class="vsc-fake-video vsc-fake-a">
        <div class="vsc-scan"></div>
        <span class="vsc-tag">RAW</span>
      </div>
    </div>
    <div class="vsc-layer vsc-layer-after" id="vscAfter">
      <div class="vsc-fake-video vsc-fake-b">
        <div class="vsc-scan"></div>
        <span class="vsc-tag">GRADED</span>
      </div>
    </div>
    <div class="vsc-handle" id="vscHandle">
      <div class="vsc-handle-line"></div>
      <div class="vsc-handle-grip">⟺</div>
    </div>
  </div>
  <div class="vsc-scrubber">
    <button class="vsc-play" id="vscPlay" aria-label="Play/pause">▶</button>
    <div class="vsc-bar" id="vscBar"><div class="vsc-bar-fill" id="vscBarFill"></div></div>
  </div>
</div>

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

Two fully independent controls — a comparison divider and a playback scrubber — reading separate state
The classic double-width-clip reveal trick, keeping the "after" clip pixel-aligned at any divider position
requestAnimationFrame-driven scrubber timing, the same pattern used to sync a real video's currentTime
Click-anywhere-on-the-bar seeking, not just play/pause
Unified pointer and touch dragging for the comparison divider
Arrow key support for the comparison divider, independent of playback controls

About this UI Snippet

Video Scrub Comparison Slider — Two Independent Axes of Control

Screenshot of the Video Scrub Comparison Slider snippet rendered live

A static before/after image slider only has one control: where the divider sits. This one has two, layered independently on top of each other — the *horizontal divider* controls how much of "after" is revealed over "before" (exactly like an image comparison slider), while a completely separate *scrubber bar underneath* controls where both clips are in their own timeline. Dragging the divider left or right never touches playback position, and scrubbing the bar never touches the divider — the two controls read from and write to entirely different pieces of state.

Why the "after" layer is double-width, not full-width

.vsc-layer-after is clipped to width: splitPct% — but the actual clip inside it (.vsc-fake-video) is fixed at 200% of *that* parent's width, not of the frame. That's the standard reveal-slider trick: because the inner clip is sized relative to the full frame while its parent is clipped to a fraction of it, the visible portion of the "after" clip always lines up pixel-for-pixel with the "before" layer underneath, at any divider position — the clip doesn't scale or distort as you drag, only how much of it is revealed changes.

A scrubber timed with requestAnimationFrame, not a video element

This snippet illustrates the pattern with CSS-animated placeholder "clips" rather than real <video> elements, so the scrubber bar's fill is driven by requestAnimationFrame computing elapsed time against a fixed DURATION — in a real implementation, that same scrubber would instead read and write a real <video> element's .currentTime, keeping the two videos' playback perfectly synced to each other since both would share the exact same scrub position.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA comparison frame appears with a "RAW" clip on the left and "GRADED" clip revealed on the right, divider at center.
  2. 2
    Drag the dividerSlide left or right to reveal more of either clip — this never affects playback position.
  3. 3
    Click PlayThe bottom scrubber bar starts filling, representing shared playback position for both clips.
  4. 4
    Click anywhere on the scrubber barJump playback to that point instantly.
  5. 5
    Use Left/Right arrow keysNudge the comparison divider without touching the scrubber.

Real-world uses

Common Use Cases

🎨
Colour grading showcases
Show a raw clip against a graded version with a draggable divider, while a separate scrubber moves both videos through time together.
💻
Software demo comparisons
Compare an old interface recording with a new one, using the double-width clip trick so the after video stays pixel-aligned.
✨
Video filter marketing pages
Let visitors drag to see exactly what an effect does to moving footage, which a still image cannot convey.
🎞️
Restoration and tutorial comparisons
Show a restored clip beside its original, with click-anywhere seeking on the bar instead of only play and pause.
🖼️
Still image alternative
Use the image comparison slider instead when the content is a photograph and no timeline is needed.

Got questions?

Frequently Asked Questions

Replace each .vsc-fake-video with a <video> element (both sized/cloned identically), and inside the scrubber bar's click handler and the play button, set both videos' .currentTime and call .play()/.pause() on both together — since they share the same DURATION and position, keep them in sync by driving both from one shared time value rather than letting each video run its own clock independently.

The parent .vsc-layer-after is clipped to only splitPct% of the frame's width — without compensating, the clip inside it would also shrink to that width and look squeezed. Setting the inner clip to 200% (double the frame's full width) and letting the parent clip it means the visible slice always represents the correct, undistorted portion of the full-width clip.

Yes — swap the width-based clipping for a height-based one (clip .vsc-layer-after to a percentage height instead of width), and change the pointer tracking from clientX/rect.width to clientY/rect.height.

Drive both from one shared time value on every scrubber update rather than letting each element play independently — set both videos' currentTime together, and consider listening for one video's timeupdate event to correct any drift in the other.

The play button and scrubber bar are real interactive elements; for full accessibility, add aria-valuenow reflecting scrubber position, make the comparison handle keyboard-focusable with its own aria-label, and provide captions/transcripts for any real video content used.