A carousel using Framer Motion, featuring smooth animations and transitions for an engaging slide experience
npm install framer-motion lucide-react
"use client";import { useState } from "react";import { motion, useMotionValue } from "framer-motion";import { ArrowLeft, ArrowRight } from "lucide-react";import { cn } from "@/lib/utils";interface SwipperProps {images: string[];className?: string;}export const Swiper: React.FC<SwipperProps> = ({ images, className }) => {const [imgIndex, setImgIndex] = useState(0);const dragX = useMotionValue(0);const onDragEnd = () => {const x = dragX.get();if (x <= -10 && imgIndex < images.length - 1) {setImgIndex(imgIndex + 1);} else if (x >= 10 && imgIndex > 0) {setImgIndex(imgIndex - 1);}};return (<divclassName={cn("group relative aspect-square h-full w-full overflow-hidden rounded-lg",className)}><div className="pointer-events-none absolute top-1/2 z-10 flex w-full -translate-y-1/2 justify-between px-5"><buttonstyle={{ opacity: imgIndex === 0 ? 0 : 1 }}className="pointer-events-auto h-fit w-fit rounded-full bg-white/80 p-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100"onClick={() => imgIndex > 0 && setImgIndex(imgIndex - 1)}><ArrowLeft className="stroke-neutral-600" size={20} /></button><buttonstyle={{ opacity: imgIndex === images.length - 1 ? 0 : 1 }}className="pointer-events-auto h-fit w-fit rounded-full bg-white/80 p-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100"onClick={() =>imgIndex < images.length - 1 && setImgIndex(imgIndex + 1)}><ArrowRight className="stroke-neutral-600" size={20} /></button></div><div className="pointer-events-none absolute bottom-2 z-10 flex w-full items-center justify-center"><div className="flex items-center justify-center rounded-md bg-black/50 p-1 text-xs text-white opacity-0 transition-opacity duration-300 group-hover:opacity-100">{imgIndex + 1} / {images.length}</div></div><motion.divdrag="x"dragConstraints={{ left: 0, right: 0 }}dragMomentum={false}style={{ x: dragX }}animate={{ translateX: `-${imgIndex * 100}%` }}onDragEnd={onDragEnd}transition={{damping: 18,stiffness: 90,type: "spring",duration: 0.15,}}className="flex h-full cursor-grab items-center rounded-[inherit] active:cursor-grabbing">{images.map((src, i) => (<motion.divkey={i}className="h-full w-full shrink-0 overflow-hidden bg-neutral-800 object-cover first:rounded-l-[inherit] last:rounded-r-[inherit]"><imgsrc={src}className="pointer-events-none h-full w-full object-cover"/></motion.div>))}</motion.div></div>);};