Black Friday: Enjoy a 35% discount on the bundles. Apply the code BLACKFRIDAY35 at checkout! Limited offer.

← Back to all tutorials

How to create an image comparison slider with Tailwind CSS and JavaScript

js-image-comparison-slider
Published and written on Sep 06 2024 by Michael Andreuzza

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. Classes
  • relative: 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 w-full h-full object-cover"
   />

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. Classes
  • absolute: 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 w-full h-full object-cover"
      />
</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. Classes
  • absolute: 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 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>

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 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;

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 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);
}

The mouse move event

  • 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);
}

The mouse up event

  • isDragging = false;: This line of code sets the isDragging variable to false.
function onMouseUp() {
   isDragging = false;
}
The mouse addEventListener
  • 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);

The touch start event

The touch start event

  • 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);
});

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 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();
   });
});

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

Did you like this tutorial? Please share it with your friends!

Own all themes forever for $199.

Includes new themes, updates, unlimited projects, and lifetime support. — No subscription required!