Lexington has been awarded a grant from Astro, to celebrate. Get a 30% discount. Apply code LEXINGTON30 ( uppercase ) at checkout.
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!
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.
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.
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>
The before and after images are positioned relative to the wrapper using the relative
class.
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 w-full h-full object-cover"
/>
The after image is positioned relative to the before image using the relative
class. This image it’s wrapped in a 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.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 w-full h-full object-cover"
/>
</div>
The slider ios made of a div with a handle that can be dragged to move the before and after images.
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 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 top-1/2 left-1/2 w-8 h-8 -mt-4 -ml-4 bg-white rounded-full shadow-md flex items-center justify-center">
<!-- 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 top-1/2 left-1/2 w-8 h-8 -mt-4 -ml-4 bg-white rounded-full shadow-md flex items-center justify-center">
<!-- 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 w-full h-full object-cover"
/>
<!-- 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 w-full h-full object-cover"
/>
</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 top-1/2 left-1/2 w-8 h-8 -mt-4 -ml-4 bg-white rounded-full shadow-md flex items-center justify-center">
<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-neutral-600">
<path d="M18 8L22 12L18 16"></path>
<path d="M6 8L2 12L6 16"></path>
</svg>
</div>
</div>
</div>
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 ID image-comparison
.const overlay = document.getElementById("image-overlay");
: This line of code selects the overlay element with the ID image-overlay
.const handle = document.getElementById("slider-handle");
: This line of code selects the handle element with the ID slider-handle
.let isDragging = false;
: This line of code declares a variable isDragging
and initializes it to false
.const container = document.getElementById("image-comparison");
const overlay = document.getElementById("image-overlay");
const handle = document.getElementById("slider-handle");
let isDragging = false;
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}%`;
}
isDragging = true;
: This line of code sets the isDragging
variable to true
.updateSliderPosition(e.clientX);
: This line of code calls the updateSliderPosition
function with the clientX
property of the mouse event.function onMouseDown(e) {
isDragging = true;
updateSliderPosition(e.clientX);
}
if (!isDragging) return;
: This line of code checks if the isDragging
variable is true
.updateSliderPosition(e.clientX);
: This line of code calls the updateSliderPosition
function with the clientX
property of the mouse event.function onMouseMove(e) {
if (!isDragging) return;
updateSliderPosition(e.clientX);
}
isDragging = false;
: This line of code sets the isDragging
variable to false
.function onMouseUp() {
isDragging = false;
}
handle.addEventListener("mousedown", onMouseDown);
: This line of code adds a mousedown
event listener to the handle element.document.addEventListener("mousemove", onMouseMove);
: This line of code adds a mousemove
event listener to the document object.document.addEventListener("mouseup", onMouseUp);
: This line of code adds a mouseup
event listener to the document object.handle.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
isDragging = true;
: This line of code sets the isDragging
variable to true
.updateSliderPosition(e.touches[0].clientX);
: This line of code calls the updateSliderPosition
function with the clientX
property of the first touch event.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);
handle.setAttribute("tabindex", "0");
: This line of code sets the tabindex
attribute of the handle element to 0
.handle.addEventListener("keydown", (e) => {
: This line of code adds a keydown
event listener to the handle element.const step = 1;
: This line of code declares a constant step
and initializes it to 1
.let newPercentage;
: This line of code declares a variable newPercentage
and initializes it to undefined
.if (e.key === "ArrowLeft") {
: This line of code checks if the key
property of the keyboard event is equal to "ArrowLeft"
.newPercentage = Math.max(0, parseFloat(overlay.style.width || "50") - step);
: This line of code sets the newPercentage
variable to the maximum of 0
and the result of subtracting step
from the width
property of the overlay element or 50
if the width
property is undefined
.} else if (e.key === "ArrowRight") {
: This line of code checks if the key
property of the keyboard event is equal to "ArrowRight"
.newPercentage = Math.min(100, parseFloat(overlay.style.width || "50") + step);
: This line of code sets the newPercentage
variable to the minimum of 100
and the result of adding step
to the width
property of the overlay element or 50
if the width
property is undefined
.} else {
: This line of code checks if the key
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 the newPercentage
variable.handle.style.left =
${newPercentage}%;
: This line of code sets the left position of the handle to the newPercentage
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();
});
});
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
Get lifetime access to every theme available today for $199 and own
them forever. Plus, new themes, lifetime updates, use on unlimited
projects and enjoy lifetime support.
— No subscription required!