More Layouts Snippets
Image Comparison Slider — Free HTML CSS JS Snippet
Before / After Slider · Layouts · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Image Comparison Slider — clip-path Reveal, Drag Handle & Touch Events

A before/after image comparison slider lets users drag a handle to reveal two different versions of the same image side by side — a before and after of a photo edit (as from the image filter editor), an AI enhancement, a design change, or a product transformation. This snippet implements the complete interaction: drag handle, clip-path reveal, mouse and touch events, and percentage clamping.
The clip-path reveal technique
Both images are absolutely positioned to fill the same container. The .before image (left side) is always fully visible as the background. The .after image (right side) has clip-path: inset(0 X% 0 0) — this clips its right edge by a percentage. As the handle moves left, X% increases (more of the after image is revealed from the left); as the handle moves right, X% decreases.
The setPos function
setPos(x) converts an absolute clientX position to a percentage of the container width: pct = ((x - rect.left) / rect.width) * 100. The percentage is clamped between 5% and 95% to prevent the handle from reaching the extreme edges. after.style.clipPath = "inset(0 ${100-pct}% 0 0)" and handle.style.left = pct + '%' update both the reveal and the handle position.
Mouse and touch support
mousedown sets dragging = true. mousemove calls setPos(e.clientX) while dragging is true. mouseup and mouseleave set dragging = false. touchstart and touchmove mirror this with e.touches[0].clientX.
The clip-path reveal technique
The "after" image sits directly on top of the "before" image using position: absolute; inset: 0. clip-path: inset(0 X% 0 0) clips the right portion of the after image — X% is the drag position as a percentage of the container width. When X is 50%, the left half shows "after" and the right half shows "before". When X is 0%, the full before image is visible; at 100%, the full after image is visible. Both images have the same dimensions, so the clip reveals exactly the correct portion.
Mouse and touch drag handling
The drag handle tracks three events: mousedown (start drag), mousemove (update position), mouseup (stop drag). Touch events use touchstart, touchmove, touchend. The position is computed as (e.clientX - rect.left) / rect.width * 100, clamped between 2 and 98 to prevent the images from fully disappearing. During drag, iframes have pointer-events: none applied to prevent them from intercepting mouse events.
Customising with real images
Replace the gradient placeholder divs with real img elements or background-image CSS, as you would in a photo gallery or image lightbox. Ensure both images have the same dimensions for the comparison to work correctly. Add an alt attribute to each image for accessibility.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the clip-path math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how inset(0 X% 0 0) on the after layer reveals it from the left as the percentage changes, or why setPos clamps the percentage between 5 and 95 rather than 0 and 100. The same assistant is useful for optimizing it — ask whether attaching mousemove and touchmove listeners to the whole window (rather than just the compare container) has any performance or event-conflict implications on a page with many sliders. It's just as handy for extending the slider: ask it to add a vertical orientation mode, snap the handle to specific percentages like 25/50/75 with a light magnetic pull, or add keyboard arrow-key support for accessibility so the slider can be operated without a mouse. 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 before/after image comparison slider in plain HTML, CSS, and JavaScript using clip-path — no canvas, no library.
Requirements:
- Two full-size layers stacked in the same container: a "before" layer as the base and an "after" layer directly on top, both absolutely positioned to fill the same box.
- Clip the after layer using the CSS clip-path inset() function so that only the portion from the left edge up to a percentage is visible, with the clip updating as a percentage of the container's width.
- A visible draggable handle (a vertical line plus a circular knob) positioned at that same percentage from the left, so it always lines up exactly with the clip boundary.
- Write a single function that converts a clientX coordinate into a percentage of the container's width using the container's bounding rect, clamps that percentage to a safe range (not flush against 0 or 100) so the handle never becomes impossible to grab at the edges, and applies both the clip-path and the handle's left position from that one clamped value.
- Support both mouse and touch dragging: starting a drag on mousedown or touchstart, updating position on mousemove or touchmove while dragging is active, and ending the drag on mouseup or touchend, with the move and end listeners attached broadly enough (e.g. on the window) that dragging continues smoothly even if the pointer moves faster than the cursor can stay inside the slider's bounds.
- Disable text selection and use a col-resize cursor over the slider area so it's visually clear the whole region is draggable, not just the handle itself.Step by step
How to Use
- 1Drag the handleClick and drag the centre handle left and right to reveal the before (dark) and after (coloured) images.
- 2Replace with real imagesIn the HTML panel, replace the .before and .after div backgrounds with <img> tags or CSS background-image URLs.
- 3Change the initial splitIn the JS panel, call setPos(rect.left + rect.width * 0.5) on load to set any starting position.
- 4Change the handle styleUpdate .handle CSS: width, border colour, and the arrow icon SVG.
- 5Change the clamp rangeUpdate the Math.min(95, Math.max(5, ...)) values to allow a wider or narrower drag range.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
The .after image has clip-path: inset(0 X% 0 0). The inset values are top, right, bottom, left. Increasing right X% clips more of the right side, hiding the after image. Decreasing it reveals more. Moving the handle left increases the right clip; moving right decreases it.
setPos(x) computes pct = ((x - rect.left) / rect.width) * 100. rect.left is the container left edge. Dividing by rect.width normalises to 0-100. The result is clamped between 5 and 95 to keep the handle inside the container.
touchstart sets dragging = true. touchmove calls setPos(e.touches[0].clientX) — touches[0] is the first finger contact. This mirrors the mouse drag logic exactly.
Replace the .before and .after gradient backgrounds with background-image: url("your-before.jpg") and background-image: url("your-after.jpg") in the CSS. Or use <img> tags inside each div with position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover.
After the event listeners, call: const rect = compare.getBoundingClientRect(); setPos(rect.left + rect.width * 0.5); to start at 50%. Use 0.3 for 30% or 0.7 for 70%.
Yes. Click "JSX" for a React component. In React, use useRef on the container, after, and handle elements. Attach onMouseDown, onMouseMove, onMouseUp, onTouchStart, and onTouchMove to the container. Manage the dragging state in useRef (not useState) to avoid rerenders.