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.
Top comments (0)