Skip to content

Instantly share code, notes, and snippets.

@infrasync
Created June 12, 2024 02:05
Show Gist options
  • Save infrasync/24d3c93cd76f38536a0d587fd3e50989 to your computer and use it in GitHub Desktop.
Save infrasync/24d3c93cd76f38536a0d587fd3e50989 to your computer and use it in GitHub Desktop.
Image Carousel : tailwind + framer motion
import { useEffect, useState, useCallback } from "react";
import { motion } from "framer-motion";
import { Icon } from "@iconify/react";
type Props = {
images: string[];
isAutoPlay?: boolean;
};
const variants = {
hidden: {
filter: "blur(15px)",
opacity: 0,
transition: {
duration: 0.85,
ease: "easeInOut",
},
},
show: {
filter: "blur(0px)",
opacity: 1,
transition: {
duration: 0.85,
ease: "easeInOut",
},
},
};
export default function ImgCarousel({ images, isAutoPlay = false }: Props) {
const [current, setCurrent] = useState(0);
const [animate, setAnimate] = useState<"INITIAL" | "HIDDEN">(
"INITIAL"
); // State to control animation
const previousSlide = () => {
setAnimate("HIDDEN"); // Hide before transitioning
setTimeout(() => {
setCurrent((prev) => (prev === 0 ? images.length - 1 : prev - 1));
setAnimate("INITIAL"); // Show after transitioning
}, 526); // Half of transition duration for smooth effect
};
const nextSlide = useCallback(() => {
setAnimate("HIDDEN");
setTimeout(() => {
setCurrent((prev) => (prev === images.length - 1 ? 0 : prev + 1));
setAnimate("INITIAL");
}, 526);
}, [images]);
useEffect(() => {
if (isAutoPlay) {
const interval = setInterval(() => {
nextSlide();
}, 3000);
return () => clearInterval(interval);
}
}, [isAutoPlay, nextSlide]);
return (
<main className="container mx-auto">
<div className="relative w-full h-96 p-12">
<div className="absolute inset-0 flex items-center justify-center">
<motion.img
src={images[current]}
alt="carousel"
className="object-cover w-full h-full"
initial="hidden"
animate={
animate === "INITIAL" ? "show" : "hidden"
}
variants={variants}
/>
</div>
<div className="absolute inset-y-0 right-0 flex items-center justify-center px-5">
<button
onClick={nextSlide}
className="p-2 bg-red-800 bg-opacity-80 rounded-md"
title="Next"
>
<Icon
icon="solar:arrow-right-linear"
className="w-6 h-6 text-white"
/>
</button>
</div>
<div className="absolute inset-y-0 left-0 flex items-center justify-center px-5 rounded-full">
<button
onClick={previousSlide}
className="p-2 bg-red-800 bg-opacity-80 rounded-md"
title="Previous"
>
<Icon
icon="solar:arrow-left-linear"
className="w-6 h-6 text-white"
/>
</button>
</div>
</div>
<div className="flex flex-row mt-12 gap-3 align-middle justify-center items-center">
{images.map((_, index) => (
<button>
{
current === index ? (
<Icon
key={index}
icon="octicon:dot-fill-24"
className="w-6 h-6 text-gray-800"
/>
) : (
<Icon
key={index}
icon="octicon:dot-24"
className="w-6 h-6 text-gray-800"
onClick={() => {
setAnimate("HIDDEN");
setTimeout(() => {
setCurrent(index);
setAnimate("INITIAL");
}, 526);
}}
/>
)
}
</button>
))}
</div>
</main>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment