← Back toDrag and drop in React

Create an image comparison slider

Written byPhuoc Nguyen
Created
02 Nov, 2023
Last updated
10 Nov, 2023
Tags
React image comparison slider
An image comparison slider is a powerful tool that shows the differences between two images in an easy-to-understand way. It's perfect for showcasing an image before and after some changes have been made. The slider places two images side by side, with a draggable handle in the middle that moves back and forth between the two images. This tool is commonly used in web design, photography, and other visual arts to showcase before-and-after transformations.
Using an image comparison slider on a website or application has several benefits. Firstly, it helps to showcase the differences between two images in a clear and visually appealing way. This is especially useful for businesses that want to highlight changes made to a product or service over time.
Additionally, image sliders can create engaging before-and-after stories that capture users' attention and help them understand the value of a particular product or service. By presenting information in this way, businesses can increase engagement and improve their chances of converting visitors into customers.
Another benefit of using an image comparison slider is that it allows users to interact with content in a meaningful way. Rather than simply looking at two static images side by side, users can actively engage with the content by moving the slider back and forth. This creates a more immersive experience and helps to keep users engaged for longer periods.
Overall, there are many benefits to using an image comparison slider on a website or application. Whether you're showcasing before-and-after transformations or creating engaging interactive content, this tool is sure to deliver impressive results.
In this post, we'll learn how to create an image comparison slider with React. Let's dive in and get started!
Thank you, Tron Le, for capturing the beauty of Ho Chi Minh city, my hometown.

Setting up the layout

The layout includes two images and a slider that can slide to the left or right. There are different ways to achieve this layout, which we will explore in this section.
First, we can organize the comparison slider as follows:
tsx
<div className="comparison">
<div className="comparison__first">
<img className="comparison__image" src="..." />
</div>
<div className="comparison__resizer" />
<img className="comparison__image" src="..." />
</div>
The slider is initially displayed in the center of the component. To position it absolutely to the root element, we can organize the layout of the comparison slider using CSS.
We set the position of the container of both images to `relative`, then position the resizer handle in the middle of the container by setting its left property to 50% and transforming it using `translate(-100%, 0)`. This will move the handle to the left by half of its width, perfectly centering it. Finally, we give the resizer a width of 2px and a height of 100% to take up the full height of the slider.
css
.comparison {
position: relative;
}

.comparison__resizer {
left: 50%;
position: absolute;
top: 0;
transform: translate(-100%, 0);

width: 2px;
height: 100%;
}
Let's talk about how to style the two images used in the comparison slider. To make sure each image takes up the full size of its container, we set the maximum height and width to 100%. We also use the `box-sizing: border-box` property to include any padding or borders in the images' overall dimensions.
To control how each image is displayed within its container, we use the `object-fit: cover` and `object-position: center center` properties. `object-fit` resizes the image to fit its container, while `object-position` specifies where the image should be positioned within the container.
By combining these properties, we can create an engaging comparison slider that showcases before-and-after transformations in a visually appealing way.
Here are the basic styles used for the images:
css
.comparison__image {
max-height: 100%;
width: 100%;
box-sizing: border-box;
object-fit: cover;
object-position: center center;
}

Using the clip-path property

When using a comparison slider, it's important to control the visibility of the images. By default, both images are fully visible, but we only want half of the first image to show up initially. To achieve this effect, we need to position the container of the first image:
css
.comparison__first {
left: 0;
position: absolute;
top: 0;

height: 100%;
width: 100%;
}
Next, we'll apply the `clip-path` property to the first image element. This property lets us clip an element's visible area based on a defined shape. In our case, we'll use the `inset()` function to create a rectangular shape with no top, right, or bottom offset, and a 50% left offset. This will clip the first image so that only its left half is visible at first.
css
.comparison__first {
clip-path: inset(0 0 0 50%);
}
We can customize the size and position of the clipped area by adjusting the values passed to `inset()`. Take a look at the raw layout without any interaction:

Using a background image

One approach to using a background image is to set the first image as the background. Here's how you can do it.
tsx
<div className="comparison">
<div
className="comparison__first"
style={{
backgroundImage: "url(...)",
}}
/>
<div className="comparison__resizer" />
<img className="comparison__image" src="..." />
</div>
The `comparison__first` class is responsible for positioning the first image in the slider. We set its position to absolute so we can move it around within its parent container. In this case, we aligned it with the left edge of its parent container by setting its `left` property to 0. We also made it take up exactly half of the width of its parent container by setting its `width` property to 50%.
css
.comparison__first {
left: 0;
position: absolute;
top: 0;

height: 100%;
width: 50%;
}
We can use CSS to set the styles for the background image of the first image element. First, we set the `background-position` property to `top left` to align the image with the top-left corner of its parent container. Then, we set `background-repeat` to `no-repeat` to prevent the image from being tiled or repeated. Finally, we use `background-size` to adjust the size of the image to fit its container. By setting it to `auto 100%`, we ensure that the image's width is adjusted automatically while keeping its aspect ratio intact, and that its height stretches to fill 100% of its parent container's height.
css
.comparison__first {
background-position: top left;
background-repeat: no-repeat;
background-size: auto 100%;
}
The styles of the images and resizer are consistent with the first approach. Overall, the layout looks good.

Wrapping images with containers

In this version, the images are wrapped in containers and styled using the `overflow` property.
tsx
<div className="comparison">
<div className="comparison__first">
<img className="comparison__image" src="..." />
</div>
<div className="comparison__second">
<img className="comparison__image" src="..." />
</div>
<div className="comparison__resizer" />
</div>
Both containers are positioned relative to the root element. The first container occupies the entire width, while the second container only takes up half the width.
css
.comparison__first {
left: 0;
position: absolute;
top: 0;

height: 100%;
width: 100%;
}
.comparison__second {
left: 0;
position: absolute;
top: 0;

height: 100%;
width: 50%;
}
As you can see, even though the container of the second image only takes up half the width of the root element, the image can still take up the entire space. This is achieved by using the `overflow` property.
css
.comparison__second {
overflow: hidden;
}
However, we currently need to set the width of the second image manually. But, we can make things easier by calculating the width dynamically, which means we won't need to set it manually anymore.
To start, we use `useRef()` hook to reference the container element and the second image element. Then, we define a function that triggers when the second image is fully loaded (using the `onLoad` event).
tsx
const containerRef = React.useRef<HTMLDivElement>();
const secondImageRef = React.useRef<HTMLImageElement>();

// Render
<div className="comparison" ref={containerRef}>
<img
className="comparison__image"
ref={secondImageRef}
onLoad={handleImageLoad}
/>
</div>
The `handleImageLoad()` function is crucial for making sure that both images are displayed with their correct aspect ratio, regardless of their original dimensions.
First, we use the image's `naturalWidth` and `naturalHeight` properties to find out its aspect ratio. Then, we get the width of the container element using `getBoundingClientRect().width`.
With these values, we calculate the correct height for the container element based on its aspect ratio. We set this height as a CSS style on the container element using string interpolation.
Finally, we use string interpolation again to set the width of the second image element to be equal to that of its container element. This ensures that both images are displayed side-by-side at their correct aspect ratios.
Here's an example of how the `handleImageLoad()` function could look like:
tsx
const handleImageLoad = () => {
const container = containerRef.current;
const secondImage = secondImageRef.current;

const naturalWidth = secondImage.naturalWidth;
const naturalHeight = secondImage.naturalHeight;
const ratio = naturalWidth / naturalHeight;

const containerWidth = container.getBoundingClientRect().width;
container.style.height = `${containerWidth / ratio}px`;
secondImage.style.width = `${containerWidth}px`;
};
By calculating these values in real-time as the screen size or image dimensions change, we can create a comparison slider that adapts to any device or screen size. This means that your slider will always look great, no matter what device your users are viewing it on.

Dragging the resizer

In this section, we'll go over how to drag the resizer using the first layout. And the best part is, you can easily apply a similar technique to the other layouts!
But before we dive in, let's take a moment to revisit a previous post where we learned how to create a draggable element. We used the `useRef()` hook to create a reference to the resizer element and attached it to the element via the `ref` attribute. We also handled the `mousedown` and `touchstart` events to make the resizer draggable on both desktop and touchscreen devices.
To jog your memory, here's a quick snippet of the code we used:
tsx
const resizerRef = React.useRef();

// Render
<div
className="comparison__resizer"
ref={resizerRef}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
Previously, we positioned draggable elements by setting their position absolutely within their container and updating their position using the `transform` property. However, we've taken a different approach for the slider component. We've eliminated internal states like `dx` and `dy`. Now, when users start dragging the resizer, we store the current width of the left side and its starting point.
tsx
const handleMouseDown = (e) => {
const startPos = {
x: e.clientX,
y: e.clientY,
};
const currentLeftWidth = parseFloat(window.getComputedStyle(resizerRef.current).left);
// ...
}, []);
In this example, we use `window.getComputedStyle()` to retrieve the current style of the resizer element. Then, we extract its `left` property as a floating-point number using `parseFloat()`. This gives us the current width of the left side of our comparison slider at the moment we start dragging.
Next, let's calculate how far the mouse moves horizontally and vertically while you keep it in motion. Instead of updating internal states like we did in previous posts, we can simply update the width of the left side.
tsx
const handleMouseMove = (e: React.MouseEvent) => {
const dx = e.clientX - startPos.x;
const dy = e.clientY - startPos.y;
updateWidth(currentLeftWidth, dx);
};
The `updateWidth` function updates the width of the first image container when the user drags the resizer. It takes two arguments: `currentLeftWidth`, the current width of the first image container, and `dx`, the horizontal distance that the mouse has moved since dragging began.
First, we get references to the container element, first image container, and resizer element. Then, we calculate the current width of the container element using its `getBoundingClientRect().width` property.
Next, we determine the new width of the left side by adding `dx` to `currentLeftWidth`. We convert this value to a percentage and make sure it's within valid bounds (between 0% and 100%).
Finally, we update the clip-path style of the first image container and the position of the resizer element accordingly. The `clip-path` style ensures that only a portion of the image on the left side is visible while dragging.
tsx
const updateWidth = (currentLeftWidth, dx) => {
const container = containerRef.current;
const firstHalfEle = firstHalfRef.current;
const resizerEle = resizerRef.current;

const containerWidth = container.getBoundingClientRect().width;
const delta = currentLeftWidth + dx;
const newFirstHalfWidth = (delta * 100) / containerWidth;
const normalizedWidth = Math.min(Math.max(0, newFirstHalfWidth), 100);

firstHalfEle.style.clipPath = `inset(0 0 0 ${normalizedWidth}%)`;
resizerEle.style.left = `${normalizedWidth}%`;
};
With this feature, users can simply drag the resizer and watch as the left panel's width changes dynamically and smoothly in response. Check out the final demo below to see it in action!

Conclusion

In conclusion, we've learned how to create a responsive image comparison slider with drag-and-drop functionality. We've explored three different layouts for the slider and how to handle resizer events to make it draggable.
Using React and CSS, we can create a flexible and powerful image comparison slider that works on any device or screen size. Whether you're building an e-commerce site, an online portfolio, or just want a cool way to compare images, this technique is sure to come in handy.
So, give these techniques a try in your own projects! With a little creativity and some careful design work, you can create stunning image sliders that are both beautiful and functional.
If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn