Docs
Swipe Carousel
A Swipe Carousel is a slider component that allows users to swipe through multiple items smoothly, often used for images or content navigation.
Installation
Install dependencies
npm install framer-motion lucide-react
Run the following command
It will create a new file swipe-carousel.tsx
inside the components/mage-ui/carousel
directory.
mkdir -p components/mage-ui/carousel && touch components/mage-ui/carousel/swipe-carousel.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
import { motion, useMotionValue } from "framer-motion";
const imgs = [
"https://images.unsplash.com/photo-1635373670332-43ea883bb081?q=80&w=2781&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1576174464184-fb78fe882bfd?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1503751071777-d2918b21bbd9?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1620428268482-cf1851a36764?q=80&w=2609&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1602212096437-d0af1ce0553e?q=80&w=2671&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1622313762347-3c09fe5f2719?q=80&w=2640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
const ONE_SECOND = 1000;
const AUTO_DELAY = ONE_SECOND * 5;
const DRAG_BUFFER = 50;
const SPRING_OPTIONS = {
type: "spring",
mass: 3,
stiffness: 400,
damping: 50,
};
const SwipeCarousel = () => {
const [imgIndex, setImgIndex] = useState(0);
const dragX = useMotionValue(0);
useEffect(() => {
const intervalRef = setInterval(() => {
if (dragX.get() === 0) {
setImgIndex((prev) => (prev === imgs.length - 1 ? 0 : prev + 1));
}
}, AUTO_DELAY);
return () => clearInterval(intervalRef);
}, [dragX]);
const onDragEnd = () => {
const x = dragX.get();
if (x <= -DRAG_BUFFER && imgIndex < imgs.length - 1) {
setImgIndex((prev) => prev + 1);
} else if (x >= DRAG_BUFFER && imgIndex > 0) {
setImgIndex((prev) => prev - 1);
}
};
return (
<div className="relative overflow-hidden bg-neutral-950 py-8">
<motion.div
drag="x"
dragConstraints={{ left: 0, right: 0 }}
style={{ x: dragX }}
animate={{ translateX: `-${imgIndex * 100}%` }}
transition={SPRING_OPTIONS}
onDragEnd={onDragEnd}
className="flex cursor-grab items-center active:cursor-grabbing"
>
<Images imgIndex={imgIndex} />
</motion.div>
<Dots imgIndex={imgIndex} setImgIndex={setImgIndex} />
<GradientEdges />
</div>
);
};
const Images = ({ imgIndex }: { imgIndex: number }) => {
return (
<>
{imgs.map((imgSrc, idx) => (
<motion.div
key={idx}
style={{
backgroundImage: `url(${imgSrc})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
animate={{ scale: imgIndex === idx ? 0.95 : 0.85 }}
transition={SPRING_OPTIONS}
className="aspect-video w-[93vw] shrink-0 rounded-xl bg-neutral-800 object-cover"
/>
))}
</>
);
};
const Dots = ({
imgIndex,
setImgIndex,
}: {
imgIndex: number;
setImgIndex: Dispatch<SetStateAction<number>>;
}) => {
return (
<div className="mt-4 flex w-full justify-center gap-2">
{imgs.map((_, idx) => (
<button
key={idx}
onClick={() => setImgIndex(idx)}
className={`h-3 w-3 rounded-full transition-colors ${idx === imgIndex ? "bg-neutral-50" : "bg-neutral-500"
}`}
/>
))}
</div>
);
};
const GradientEdges = () => (
<>
<div className="pointer-events-none absolute bottom-0 left-0 top-0 w-[10vw] max-w-[100px] bg-gradient-to-r from-neutral-950/50 to-neutral-950/0" />
<div className="pointer-events-none absolute bottom-0 right-0 top-0 w-[10vw] max-w-[100px] bg-gradient-to-l from-neutral-950/50 to-neutral-950/0" />
</>
);
export default SwipeCarousel;