
Ever wanted to show off “before and after” images in a way that’s both smooth and interactive? Today, we’re going to build a cool image comparison slider using JavaScript and Tailwind CSS.
This neat little feature is perfect for highlighting changes—whether it’s editing photos, showing off product updates, or just comparing two images side by side. It’s simple to set up, and it adds a touch of polish to any project. Let’s get started and see how easy it is to make!
What are image comparison sliders?
An image comparison slider is an interactive UI component that allows users to compare two images by sliding a handle horizontally or vertically. Typically, one image is placed on top of another, and users can move the slider handle to reveal more or less of each image. This component is widely used to highlight differences or changes between two images, such as before and after photos.
Use cases
Image comparison sliders have several practical applications, including:
-
Before and After Comparisons: Commonly used in industries like photography, design, and medical imaging to show transformations such as photo edits, home renovations, or facial treatments.
-
Product Feature Comparison: Helps showcase product upgrades or new features by comparing an old product version to a new one visually.
-
Historical Changes: Used in journalism, education, or documentaries to compare changes over time, such as landscapes, cityscapes, or environmental impacts.
-
Scientific Analysis: In scientific presentations, an image slider is useful for showing microscopic or medical imaging changes, such as tissue differences before and after treatment.
Lets get to the code
Thre wrapper
The wrapper is the container for the before and after images and the slider handle. It has a fixed height and width. Id’s
id="image-comparison"
: Assigns a unique ID to the wrapper. Classesrelative
: Sets the wrapper as a relative element. This allows us to position the before and after images and the slider handle relative to the wrapper.h-[300px] sm:h-[400px] md:h-[500px]
: Sets the height of the wrapper to 300px on small screens, 400px on medium screens, and 500px on large screens.overflow-hidden
: Hides any overflowing content within the wrapper. This is important for the before and after images to be displayed properly.w-full
: Sets the width of the wrapper to 100%.max-w-3xl
: Sets the maximum width of the wrapper to 3xl.
<div
id="image-comparison"
class="relative h-[300px] sm:h-[400px] md:h-[500px] overflow-hidden w-full max-w-3xl mx-auto ">
<!-- Your content here -->
</div>
Before and After Images
The before and after images are positioned relative to the wrapper using the relative
class.
Teh before image
absolute
: Sets the before image to be positioned absolutely within the wrapper.top-0
: Sets the top position of the before image to 0.left-0
: Sets the left position of the before image to 0.w-full
: Sets the width of the before image to 100%.h-full
: Sets the height of the before image to 100%.
<img
src="...."
alt="#_"
class="absolute top-0 left-0 object-cover w-full h-full"
/>
The after image
The after image is positioned relative to the before image using the relative
class. This image it’s wrapped in a container.
The after image container
Id’s
id="image-overlay"
: Assigns a unique ID to the after image container. This is used to target the after image and its container. Classesabsolute
: Sets the after image container to be positioned absolutely within the wrapper.top-0
: Sets the top position of the after image container to 0.left-0
: Sets the left position of the after image container to 0.w-1/2
: Sets the width of the after image container to 50%.h-full
: Sets the height of the after image container to 100%.overflow-hidden
: Hides any overflowing content within the after image container.
The after image
absolute
: Sets the after image to be positioned absolutely within the after image container.top-0
: Sets the top position of the after image to 0.left-0
: Sets the left position of the after image to 0.w-full
: Sets the width of the after image to 100%.h-full
: Sets the height of the after image to 100%.object-cover
: Sets the after image to cover the entire container.
<div
id="image-overlay"
class="absolute top-0 left-0 w-1/2 h-full overflow-hidden">
<img
src="..."
alt="#_"
class="absolute top-0 left-0 object-cover w-full h-full"
/>
</div>
The slide handle
The slider ios made of a div with a handle that can be dragged to move the before and after images.
The slider wrapper
Id’s
id="slider-handle"
: Assigns a unique ID to the slide handle. This is used to target the slide handle. Classesabsolute
: Sets the slide handle to be positioned absolutely within the wrapper.top-0
: Sets the top position of the slide handle to 0.bottom-0
: Sets the bottom position of the slide handle to 0.w-1
: Sets the width of the slide handle to 1px.cursor-ew-resize
: Sets the cursor style of the slide handle to be a resize cursor.style="left: 50%;
: Sets the left position of the slide handle to 50%.
<div
id="slider-handle"
class="absolute top-0 bottom-0 w-1 bg-white cursor-ew-resize"
style="left: 50%;">
<!-- Your content here -->
</div>
The handle itself
The handle is a div with a an SVG inside.
absolute
: Sets the handle to be positioned absolutely within the slide handle.top-1/2
: Sets the top position of the handle to 50%.left-1/2
: Sets the left position of the handle to 50%.w-8
: Sets the width of the handle to 8px.h-8
: Sets the height of the handle to 8px.-mt-4
: Sets the top margin of the handle to -4px.-ml-4
: Sets the left margin of the handle to -4px.flex
: Sets the handle to be a flex container.items-center
: Centers the handle vertically.justify-center
: Centers the handle horizontally.
<div
class="absolute flex items-center justify-center w-8 h-8 -mt-4 -ml-4 bg-white rounded-full shadow-md top-1/2 left-1/2">
<!-- SVG goes here -->
</div>
<div
id="slider-handle"
class="absolute top-0 bottom-0 w-1 bg-white cursor-ew-resize"
style="left: 50%;">
<div
class="absolute flex items-center justify-center w-8 h-8 -mt-4 -ml-4 bg-white rounded-full shadow-md top-1/2 left-1/2">
<!-- SVG goes here -->
</div>
</div>
<div
id="image-comparison"
class="relative h-[300px] sm:h-[400px] md:h-[500px] overflow-hidden w-full max-w-3xl mx-auto mt-8 pt-6 border-t">
<!-- Before Image -->
<img
src="https://i.pinimg.com/736x/c5/02/db/c502dbc27ec77fb1343e798f855901c7.jpg"
alt="Before Image"
class="absolute top-0 left-0 object-cover w-full h-full"
/>
<!-- After Image (in overlay) -->
<div
id="image-overlay"
class="absolute top-0 left-0 w-1/2 h-full overflow-hidden">
<img
src="https://i.pinimg.com/564x/56/17/d6/5617d6088a5794f177425ae6780041dd.jpg"
alt="After Image"
class="absolute top-0 left-0 object-cover w-full h-full"
/>
</div>
<!-- Slider Handle -->
<div
id="slider-handle"
class="absolute top-0 bottom-0 w-1 bg-white cursor-ew-resize"
style="left: 50%;">
<div
class="absolute flex items-center justify-center w-8 h-8 -mt-4 -ml-4 bg-white rounded-full shadow-md top-1/2 left-1/2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-base-600">
<path d="M18 8L22 12L18 16"></path>
<path d="M6 8L2 12L6 16"></path>
</svg>
</div>
</div>
</div>
Lets get to the script’
The addEventListener
We’ll add the addEventListener
function to the document
object to listen for the DOMContentLoaded
event.
const container = document.getElementById("image-comparison");
: This line of code selects the container element with the IDimage-comparison
.const overlay = document.getElementById("image-overlay");
: This line of code selects the overlay element with the IDimage-overlay
.const handle = document.getElementById("slider-handle");
: This line of code selects the handle element with the IDslider-handle
.let isDragging = false;
: This line of code declares a variableisDragging
and initializes it tofalse
.
const container = document.getElementById("image-comparison");
const overlay = document.getElementById("image-overlay");
const handle = document.getElementById("slider-handle");
let isDragging = false;
The updateSliderPosition function
const containerRect = container.getBoundingClientRect();
: This line of code gets the bounding client rect of the container element.const containerWidth = containerRect.width;
: This line of code gets the width of the container element.const position = Math.max(0, Math.min(x - containerRect.left, containerWidth));
: This line of code calculates the position of the handle based on the x position of the mouse or touch event.const percentage = (position / containerWidth) * 100;
: This line of code calculates the percentage of the handle based on the position of the handle.overlay.style.width =
${percentage}%;
: This line of code sets the width of the overlay element to the percentage of the handle.handle.style.left =
${percentage}%;
: This line of code sets the left position of the handle to the percentage of the handle.
function updateSliderPosition(x) {
const containerRect = container.getBoundingClientRect();
const containerWidth = containerRect.width;
const position = Math.max(
0,
Math.min(x - containerRect.left, containerWidth)
);
const percentage = (position / containerWidth) * 100;
overlay.style.width = `${percentage}%`;
handle.style.left = `${percentage}%`;
}
The mouse and touch events
The mouse down event
isDragging = true;
: This line of code sets theisDragging
variable totrue
.updateSliderPosition(e.clientX);
: This line of code calls theupdateSliderPosition
function with theclientX
property of the mouse event.
function onMouseDown(e) {
isDragging = true;
updateSliderPosition(e.clientX);
}
The mouse move event
if (!isDragging) return;
: This line of code checks if theisDragging
variable istrue
.updateSliderPosition(e.clientX);
: This line of code calls theupdateSliderPosition
function with theclientX
property of the mouse event.
function onMouseMove(e) {
if (!isDragging) return;
updateSliderPosition(e.clientX);
}
The mouse up event
isDragging = false;
: This line of code sets theisDragging
variable tofalse
.
function onMouseUp() {
isDragging = false;
}
The mouse addEventListener
handle.addEventListener("mousedown", onMouseDown);
: This line of code adds amousedown
event listener to the handle element.document.addEventListener("mousemove", onMouseMove);
: This line of code adds amousemove
event listener to the document object.document.addEventListener("mouseup", onMouseUp);
: This line of code adds amouseup
event listener to the document object.
handle.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
The touch start event
The touch start event
isDragging = true;
: This line of code sets theisDragging
variable totrue
.updateSliderPosition(e.touches[0].clientX);
: This line of code calls theupdateSliderPosition
function with theclientX
property of the first touch event.
handle.addEventListener("touchstart", (e) => {
isDragging = true;
updateSliderPosition(e.touches[0].clientX);
});
The touch move event
document.addEventListener("touchmove", (e) => {
if (!isDragging) return;
updateSliderPosition(e.touches[0].clientX);
});
The touch end event
document.addEventListener("touchend", onMouseUp);
The keyboard accessibility
handle.setAttribute("tabindex", "0");
: This line of code sets thetabindex
attribute of the handle element to0
.handle.addEventListener("keydown", (e) => {
: This line of code adds akeydown
event listener to the handle element.const step = 1;
: This line of code declares a constantstep
and initializes it to1
.let newPercentage;
: This line of code declares a variablenewPercentage
and initializes it toundefined
.if (e.key === "ArrowLeft") {
: This line of code checks if thekey
property of the keyboard event is equal to"ArrowLeft"
.newPercentage = Math.max(0, parseFloat(overlay.style.width || "50") - step);
: This line of code sets thenewPercentage
variable to the maximum of0
and the result of subtractingstep
from thewidth
property of the overlay element or50
if thewidth
property isundefined
.} else if (e.key === "ArrowRight") {
: This line of code checks if thekey
property of the keyboard event is equal to"ArrowRight"
.newPercentage = Math.min(100, parseFloat(overlay.style.width || "50") + step);
: This line of code sets thenewPercentage
variable to the minimum of100
and the result of addingstep
to thewidth
property of the overlay element or50
if thewidth
property isundefined
.} else {
: This line of code checks if thekey
property of the keyboard event is not equal to"ArrowLeft"
or"ArrowRight"
.return;
: This line of code returns from the function.overlay.style.width =
${newPercentage}%;
: This line of code sets the width of the overlay element to thenewPercentage
variable.handle.style.left =
${newPercentage}%;
: This line of code sets the left position of the handle to thenewPercentage
variable.e.preventDefault();
: This line of code prevents the default behavior of the keyboard event.
handle.setAttribute("tabindex", "0");
handle.addEventListener("keydown", (e) => {
const step = 1;
let newPercentage;
if (e.key === "ArrowLeft") {
newPercentage = Math.max(
0,
parseFloat(overlay.style.width || "50") - step
);
} else if (e.key === "ArrowRight") {
newPercentage = Math.min(
100,
parseFloat(overlay.style.width || "50") + step
);
} else {
return;
}
overlay.style.width = `${newPercentage}%`;
handle.style.left = `${newPercentage}%`;
e.preventDefault();
});
document.addEventListener("DOMContentLoaded", function () {
const container = document.getElementById("image-comparison");
const overlay = document.getElementById("image-overlay");
const handle = document.getElementById("slider-handle");
let isDragging = false;
function updateSliderPosition(x) {
const containerRect = container.getBoundingClientRect();
const containerWidth = containerRect.width;
const position = Math.max(
0,
Math.min(x - containerRect.left, containerWidth)
);
const percentage = (position / containerWidth) * 100;
overlay.style.width = `${percentage}%`;
handle.style.left = `${percentage}%`;
}
function onMouseDown(e) {
isDragging = true;
updateSliderPosition(e.clientX);
}
function onMouseMove(e) {
if (!isDragging) return;
updateSliderPosition(e.clientX);
}
function onMouseUp() {
isDragging = false;
}
handle.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
// Touch events
handle.addEventListener("touchstart", (e) => {
isDragging = true;
updateSliderPosition(e.touches[0].clientX);
});
document.addEventListener("touchmove", (e) => {
if (!isDragging) return;
updateSliderPosition(e.touches[0].clientX);
});
document.addEventListener("touchend", onMouseUp);
// Keyboard accessibility
handle.setAttribute("tabindex", "0");
handle.addEventListener("keydown", (e) => {
const step = 1;
let newPercentage;
if (e.key === "ArrowLeft") {
newPercentage = Math.max(
0,
parseFloat(overlay.style.width || "50") - step
);
} else if (e.key === "ArrowRight") {
newPercentage = Math.min(
100,
parseFloat(overlay.style.width || "50") + step
);
} else {
return;
}
overlay.style.width = `${newPercentage}%`;
handle.style.left = `${newPercentage}%`;
e.preventDefault();
});
});
Conclusion
In this tutorial, we learned how to create an image comparison slider using JavaScript and Tailwind CSS. We saw how to add interactivity to the slider by adding mouse and touch events, as well as keyboard accessibility.
I hope you found this tutorial helpful and have a great day!
/Michael Andreuzza
Unlock all themes for $149 and own them forever! Includes lifetime updates, new themes, unlimited projects, and support
