Making a Responsive Image Comparison Slider in CSS and JavaScript

Making a Responsive Image Comparison Slider in CSS and JavaScript

TL;DR:

We have created a lightweight, responsive image comparison slider that works on any layout and size. It’s open source: Go and grab it here. Codepen demo here.

Image Comparison Sliders are a great way to present before and after cases, this is especially the case when the differences are very small.

A month ago Adobe FormsCentral announced its closure. We knew that hard times would be ahead for FormCentral users so we decided to build an import tool that would ease the transfer process to JotForm. We quickly built a FormsCentral Import page to welcome new FormsCentral users to our service.

The New Import Wizard ended up being so good that the differences between the original and imported forms were barely noticeable. We first tried to show the before and after versions in two separate screenshots side by side, but they were so similar it looked like we used the same image twice. We needed a creative way to show the barely noticeable differences. So we decided to use a comparison slider.

Comparison sliders are nothing new. There are even pure CSS implementations that are the works of true masters. However, I wasn’t able to find a truly responsive one that was simple enough for our needs. This gave me the chance (and excuse) to build one myself that can be used anywhere.

This is the story of how we built an
image slider for our FormsCentral Import page.


SETTING THE STAGE

While I’m sure this can be handled without any dependencies, I used jQuery for ease of development.

Let’s start. First up, the HTML:

<div class="ba-slider"> <img src="http://i.imgur.com/BIHN8KQ.jpg"> <div class="resize"> <img src="http://i.imgur.com/nS6dvpq.jpg"> </div> <span class="handle"></span> </div>

Try it.

This, unfortunately, can not get any simpler for the purposes of a truly responsive comparison slider. The extra .resize div is needed for clipping the second image. Hopefully, with better clip-path support in the future, this can be done without it. In fact, if shadow-dom manipulation in JS picked up some pace along with clip-path, this can be done in just 2 divs using pseudo-elements. But until then, this is what we have.

Similarly, Lea Verou’s pure css implementation uses the resize property which can only initiate the drag interaction on a relatively smaller area. Even with the stretched version mentioned in her edit, the styling options are highly limited.

Next up, we set the stage by adding some CSS.

.ba-slider { position: relative; overflow: hidden; } .ba-slider img { width: 100%; display:block; } .ba-slider .resize { position: absolute; top:0; left: 0; height: 100%; width: 50%; overflow: hidden; }

Try it.

The main issue in making a comparison slider responsive is placing the second image on top of the first one and declaring a width based on a grandparent (.slider in this case). Dudley Storey circumvents this by using vw and vh units and setting max-height and max-width, however, this is not always ideal when working with a grid, i.e. bootstrap.

That’s why I’ve decided to add this script to manually set the width for the second image on page load. This will allow us to not worry about fine-detailing the slider itself for each responsive breakpoint. It will simply fit into your grid.

// Call & init $(document).ready(function(){ $('.ba-slider').each(function(){ var cur = $(this); // Adjust the slider var width = cur.width()+'px'; cur.find('.resize img').css('width', width); }); });

Try it.

ADDING THE INTERACTION

Now that the groundwork has been laid, we can move onto the sliding action. Let’s start by styling the knob and the separator:

.handle {
position:absolute;
left:50%;
top:0;
bottom:0;
width:4px;
margin-left:-2px;
background: rgba(0,0,0,.5);
cursor: ew-resize;
}
.handle:after {
position: absolute;
top: 50%;
width: 64px;
height: 64px;
margin: -32px 0 0 -32px;
content:'\21d4';
color:white;
font-weight:bold;
font-size:36px;
text-align:center;
line-height:64px;
background: #ffb800; /* @orange */
border:1px solid #e6a600; /* darken(@orange, 5%) */
border-radius: 50%;
transition:all 0.3s ease;
box-shadow:
0 2px 6px rgba(0,0,0,.3),
inset 0 2px 0 rgba(255,255,255,.5),
inset 0 60px 50px -30px #ffd466; /* lighten(@orange, 20%)*/ }
.handle.draggable:after {
width: 48px;
height: 48px;
margin: -24px 0 0 -24px;
line-height:50px;
font-size:30px;
}

Try it.

So everything is pretty much set, we just need to make this come to life by implementing the drag functionality. Claudia Romano over at CodyHouse pretty much perfected this, so I’ll be using her function here with some small improvements.

Basically, we’ll find the initial position on mousedown, listen to mousemove for calculating and applying the drag distance and finally bind the mouseup events to stop dragging.

function drags(dragElement, resizeElement, container) {
// Initialize the dragging event on mousedown.
dragElement.on('mousedown touchstart', function(e) {
dragElement.addClass('draggable');
resizeElement.addClass('resizable');
// Check if it's a mouse or touch event and pass along the correct value
var startX = (e.pageX) ? e.pageX :
e.originalEvent.touches[0].pageX;
// Get the initial position
var dragWidth = dragElement.outerWidth(),
posX = dragElement.offset().left + dragWidth - startX,
containerOffset = container.offset().left,
containerWidth = container.outerWidth();
// Set limits
minLeft = containerOffset + 10;
maxLeft = containerOffset + containerWidth - dragWidth - 10;
// Calculate the dragging distance on mousemove.
dragElement.parents().on("mousemove touchmove", function(e) {
// Check if it's a mouse or touch event and pass along the correct value
var moveX = (e.pageX) ? e.pageX
e.originalEvent.touches[0].pageX;
leftValue = moveX + posX - dragWidth;
// Prevent going off limits
if ( leftValue < minLeft) {
leftValue = minLeft;
} else if (leftValue > maxLeft) {
leftValue = maxLeft;
}
// Translate the handle's left value to masked divs width.
widthValue = (leftValue + dragWidth/2 - containerOffset)*100/containerWidth+'%';
// Set the new values for the slider and the handle.
// Bind mouseup events to stop dragging.
$('.draggable').css('left', widthValue).on('mouseup touchend touchcancel', function () {
$(this).removeClass('draggable');
resizeElement.removeClass('resizable');
});
$('.resizable').css('width', widthValue);
}).on('mouseup touchend touchcancel', function(){
dragElement.removeClass('draggable');
resizeElement.removeClass('resizable');
});
e.preventDefault();
}).on('mouseup touchend touchcancel', function(e){
dragElement.removeClass('draggable');
resizeElement.removeClass('resizable');
});
}

Try it.

WRAPPING IT UP

Finally, we call the drags function, tie everything together on document.ready and voila!

// Call & init
$(document).ready(function(){
$('.ba-slider').each(function(){
var cur = $(this);
// Adjust the slider
var width = cur.width()+'px';
cur.find('.resize img').css('width', width);
// Bind dragging events
drags(cur.find('.handle'), cur.find('.resize'), cur);
});
});
// Update sliders on resize.
// We all do it: i.imgur.com/YkbaV.gif
$(window).resize(function(){
$('.ba-slider').each(function(){
var cur = $(this);
var width = cur.width()+'px';
cur.find('.resize img').css('width', width);
});
});

Try it.


So, there you have it. I’m already working on turning this into a jQuery plugin but, hopefully, this will help a few people in need and spark some ideas until then.

P.S. We open sourced the Before & After Image Comparison Slider under the MIT license: before-after.js. We hope it will save developers time, and that they will use in their own projects. Feel free to fork it, comment, or send pull requests.

Send Comment:

Jotform Avatar
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Comments: