How to Build a Functional Before And After Slider for Web

I spent the better part of a week last year trying to get a comparison slider to work cleanly across browsers. The end result was clean enough, but the process revealed some things most tutorials skip. Here's what I learned about building Aesthetic Before And After Threads that actually perform. They're interactive comparison components where a user drags a handle to reveal a before state versus an after state, typically used for photo editing portfolios, renovation showcases, skin care results, and design case studies. The most common implementation uses two identical stacked elements with a clip-path or width animation driven by pointer events. There are simpler versions too, like static split layouts or tab toggles, but those aren't really comparable to the interactive variant. The core mechanism is straightforward. You place two images in the same space using position relative on the container and absolute on each image. One image sits on top and gets its width constrained by a clip or transform. A draggable handle tracks the pointer position and updates that constraint in real time.

The Implementation

I built mine using vanilla JavaScript with requestAnimationFrame for the animation loop rather than jQuery or any library. It adds roughly two extra lines of code but saves about 80k of payload you don't need. Start with the HTML structure. You want a container div, two image elements inside it, and a handle element for the drag interaction. The before image is the bottom layer. The after image is positioned absolutely on top and its visible width gets adjusted as the user drags. The handle follows the same X coordinate. For the CSS, set the container to position relative with overflow hidden. Both images get position absolute with top left zero and full width and height. The after image is the one you'll clip. The handle sits on top with a z-index higher than both images and is styled however you need it visually. Don't forget pointer-events: none on the handle so it doesn't intercept the drag on the container itself.

The JavaScript tracks mousedown, touchstart, mousemove, and touchmove. When the pointer goes down on the container, you calculate the offset from the left edge of the container and clamp it between zero and the full container width. On move, you update the clip-path or width of the after image and reposition the handle. The clamp is important because otherwise the handle can drift past the edges and look broken. I initially used a simple width assignment on the after image. That worked fine on desktop but caused layout thrashing on mobile because changing width triggers repaint on every frame. Switching to transform: translateX and scaleX for the after image dropped the repaint cost significantly. On my test device, scrolling became noticeably smoother after that change. The visual result is identical but the performance profile is better on low-end hardware.

Get the Full Details

PDO Thread Lifting - Before and After Results at Skinly Aesthetics
PDO Thread Lifting - Before and After Results at Skinly Aesthetics

A Problem I Ran Into That Most Guides Miss

Here's the edge case that cost me most of a day. I was working with before and after images that had slightly different aspect ratios. The container was sized to the before image, which meant the after image had to scale to fit differently. When the user dragged near the edges, the misalignment became obvious because the two images didn't share the same pixel density across the full width. The fix was to make both images use object-fit: cover on a background-image setup rather than img elements. That way I could set the background-size to cover and the background-position to center on both images independently. The container dimensions came from the larger of the two image aspect ratios instead of locking to one. This meant the images might have slightly different crop behavior but they always aligned perfectly regardless of where the slider stopped. This also solves the DPI problem. If you serve a 1x image and someone views it on a retina display, the after image will blur. You need to detect devicePixelRatio and serve a 2x or higher resolution asset. I used a srcset with sizes attribute on the img fallback approach for browsers that don't support the background-image method.

When Aesthetic Before And After Threads Don't Work Well

There are scenarios where this component fails completely and you should pick something else. If your before and after images have very different file sizes, the loading experience is jarring. Users will see one image load instantly while the other takes seconds. The workaround is to lazy load the after image only when the user first interacts with the slider, or to preload both with equal priority and compress aggressively with WebP or AVIF. Accessibility is another real problem. Screen reader users can't drag a visual slider at all. You need to add a slider input element alongside it that mirrors the same range and value. The visual handle becomes purely decorative for assistive tech while the native range input does the actual work. That means JavaScript needs to sync both the visual handle position and the input value on every change event. Touch devices sometimes miss the drag entirely if the touch target is too small. I set the touch target to be the full width of the container rather than just the handle. The handle moves visually but the interaction area is the entire component. This feels slightly less precise on very wide screens but it prevents the common complaint where users try to grab the thin handle and fail repeatedly.

Performance Numbers Worth Knowing

A well-built version using transform and requestAnimationFrame runs at 60fps on most modern devices. The CPU impact is minimal because transforms are GPU-accelerated. Memory usage stays flat unless you're loading extremely large images. I measured around 2.4 megabytes of resident memory for a slider using two 4k images before optimization and dropped it to about 1.1 megabytes after switching to compressed WebP at 80% quality with a scaled-down max width of 1600 pixels for display. If you're using a framework, avoid React re-renders on every mousemove event. Store the position in a ref and update the DOM directly rather than triggering state changes. State-driven updates will bottleneck the slider at roughly 30fps on mid-range laptops because React batches and reconciles on every single frame.

PDO Thread Lifting - Before and After Results at Skinly Aesthetics
PDO Thread Lifting - Before and After Results at Skinly Aesthetics

What I'd Do Differently Next Time

I would add a keyboard accessible mode from the start instead of retrofitting it later. Arrow key controls for the slider position and a visible focus ring on the range input prevent the accessibility retrofit from becoming a separate project. It also took me too long to realize that the initial state should default to the before image at a specific position, usually 50 percent, rather than fully collapsed. Users expect to see both states immediately and a fully hidden after image makes the component look broken on first load. The overall build time for a production-ready version with proper touch, keyboard, and high-DPI support is roughly six to eight hours if you start from scratch and account for testing across devices. If you reuse code from a previous project and have the compression pipeline ready, it's closer to three hours. The difference is mostly in testing on actual hardware rather than the browser developer tools.