Create a Before/After Image Slider with Next.js React

Create a Before/After Image Slider with Next.js React

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

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


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