A before/after image slider in 2 lines of JavaScript

A before/after image slider in 2 lines of JavaScript Set markodenic.tech as your preferred Google source

Creem.io

Merchant of record payments for SaaS and digital products, handling checkout, subscriptions, taxes, invoices, and global payment operations.

Get started today

Every renovation company, dentist, photo editor, and cleaning service wants the same thing on their homepage: drag a handle across the photo, see the before on one side and the after on the other. It’s the single most convincing thing they can put on a page.

Search for it and you get a jQuery plugin from 2014, or a React component with mouse, touch, and pointer handlers, a getBoundingClientRect call on every move, and a bug on window resize.

It’s two stacked images, one clip-path, and a range input.

The fix

html
<div class="compare" style="--pos: 50%">
  <img src="after.jpg" alt="Kitchen after the renovation" />
  <img src="before.jpg" alt="Kitchen before the renovation" class="reveal" />
  <input type="range" min="0" max="100" value="50" aria-label="Reveal the before photo" />
</div>
css
.compare { position: relative; display: grid; }
.compare img { grid-area: 1 / 1; width: 100%; display: block; }
.compare .reveal { clip-path: inset(0 calc(100% - var(--pos)) 0 0); }
.compare input { position: absolute; bottom: 1rem; left: 0; width: 100%; }
javascript
slider.addEventListener('input', () => {
  compare.style.setProperty('--pos', `${slider.value}%`);
});

That is the entire component.

Before After

Drag the handle, or click it and use the arrow keys. The two panels here are drawn with CSS gradients instead of photos, so the page has no image files to load.

Drag it, or click it and use the arrow keys.

How the three pieces fit

display: grid with both images in grid-area: 1 / 1 stacks them without absolute positioning, so the container keeps the height of the images instead of collapsing to zero. No position: absolute, no manual height.

clip-path: inset(0 calc(100% - var(--pos)) 0 0) crops the top image from the right edge. The four values are top, right, bottom, left insets, so this cuts everything past --pos and leaves the left portion showing. At --pos: 30%, you see 30% of the before image and 70% of the after.

The range input does the dragging. It already handles mouse, touch, pen, click-to-jump, and drag-past-the-edge, because it is a native control that has been handling those since before you had this problem.

The JavaScript exists only to copy the value into a custom property. That is genuinely all of it.

Add the divider

Without a visible seam it reads as two mismatched photos rather than one comparison:

css
.compare::after {
  content: '';
  position: absolute;
  inset-block: 0;
  left: var(--pos);
  width: 2px;
  background: #fff;
  pointer-events: none;
}

pointer-events: none matters, otherwise the line swallows clicks meant for the slider underneath it.

Why not the CSS-only version

There is a well-known zero-JavaScript trick for this: put the top image in a box with overflow: hidden and resize: horizontal, and let the browser’s resize gripper act as the handle.

It works, and I would not ship it. The handle is the tiny grab corner in the bottom right, it cannot be styled, it’s invisible until you find it, and it’s completely unreachable by keyboard. Two lines of JavaScript buys you a control that announces itself as a slider, responds to arrow keys, has a Home and End key, and can be styled however the design wants.

Two details worth getting right

Both images must be the same size. Different aspect ratios and the comparison becomes a lie. Set aspect-ratio on the container to match, so nothing jumps while the images load.

Do not hide the input. Plenty of implementations set opacity: 0 on the range and draw a custom handle over it. Once you do that, focus becomes invisible and keyboard users are lost. Style the real thing with accent-color and ::-webkit-slider-thumb instead, and keep a visible focus ring.

Where this comes up

  • Home services: renovation, landscaping, cleaning, restoration. The whole sales pitch in one image.
  • Health and fitness: treatment results, progress photos.
  • Photo and video work: retouching, color grading, upscaling.
  • Product and docs: design system v1 next to v2, or a page before and after a performance fix.

Why this matters

  • A plugin for something the browser has a control for: the drag logic you were about to install is input[type="range"].
  • Keyboard and touch come free: not because you handled them, but because you did not replace the element that already does.
  • Nothing recalculates: no rect measuring, no resize listener, no pixel math. The layout is a percentage, so it just works at any width.

If you have a client whose product is a visible transformation, this is a fifteen-minute addition and it will do more for their conversion rate than a redesign.

Happy coding!

Marko

Rate this issue

Your ratings shape which issues I recommend to you.