DEV Community

Cover image for Before/After Image Slider with Next.js
The Opinionated Dev
The Opinionated Dev

Posted on

Before/After Image Slider with Next.js

Today we'll recreate a component that I saw on a fair amount of websites. It's a comparison slider or a before/after slider where we display 2 images overlapping, and allowing a user to move a slider revealing or hiding one of the images.

You can find the completed Github repository here.


We'll start with a new Next.js app, and let's go ahead and update our homepage to have this code:

// src/app/page.tsx

import { Slider } from "./components/Slider";

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <Slider />
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now that we have it, let's get to the main course and create a new file for out Slider component. In this component we'll have a both the before and after image displayed at all times.

To make this effect, we'll have to display the first 50% of image A, and the last 50% of image B. Once that's done, we'll add a button in the middle and make it draggable. As it drags, we get it's current position and set how big of a portion should we hide/display of image A and image B.

After all that, the code will look something like this

"use client";

import Image from "next/image";
import { useState } from "react";

export const Slider = () => {
  const [sliderPosition, setSliderPosition] = useState(50);
  const [isDragging, setIsDragging] = useState(false);

  const handleMove = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    if (!isDragging) return;

    const rect = event.currentTarget.getBoundingClientRect();
    const x = Math.max(0, Math.min(event.clientX - rect.left, rect.width));
    const percent = Math.max(0, Math.min((x / rect.width) * 100, 100));

    setSliderPosition(percent);
  };

  const handleMouseDown = () => {
    setIsDragging(true);
  };

  const handleMouseUp = () => {
    setIsDragging(false);
  };

  return (
    <div className="w-full relative" onMouseUp={handleMouseUp}>
      <div
        className="relative w-full max-w-[700px] aspect-[70/45] m-auto overflow-hidden select-none"
        onMouseMove={handleMove}
        onMouseDown={handleMouseDown}
      >
        <Image
          alt=""
          fill
          draggable={false}
          priority
          src="https://images.unsplash.com/photo-1523435324848-a7a613898152?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWgelHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1769&q=80"
        />

        <div
          className="absolute top-0 left-0 right-0 w-full max-w-[700px] aspect-[70/45] m-auto overflow-hidden select-none"
          style={{ clipPath: `inset(0 ${100 - sliderPosition}% 0 0)` }}
        >
          <Image
            fill
            priority
            draggable={false}
            alt=""
            src="https://images.unsplash.com/photo-1598875791852-8bb153e713f0?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWgelHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2010&q=80"
          />
        </div>
        <div
          className="absolute top-0 bottom-0 w-1 bg-white cursor-ew-resize"
          style={{
            left: `calc(${sliderPosition}% - 1px)`,
          }}
        >
          <div className="bg-white absolute rounded-full h-3 w-3 -left-1 top-[calc(50%-5px)]" />
        </div>
      </div>
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

As you can see, we have a onMouseDown and onMouseMove handler that sets our component to be in isDragging mode. This way we only change the image ratios when you actually click and drag.

In the handleMove we get the current position where we drag, calculate the new position based on that and set it in the state under sliderPosition.

Once that's done we use sliderPosition in our JSX to display the image and crop of sliderPosition/%.


I hope you enjoyed this article and it was helpful in some way. Please leave a comment if you have any questions.

You can also find the Github repository with the completed code here.

Top comments (0)