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