Component

Animated Carousel

The Animated Carousel Component offers a visually engaging and dynamic way to showcase images or content in a rotating carousel format. Perfect for portfolios, galleries, or product displays, this component provides smooth transitions and rich interactivity, making it a standout feature on any website.

Features

  • Seamless Transitions: The carousel ensures smooth and fluid transitions between slides, using Framer Motion or CSS animations to create a polished and professional feel.
  • Dynamic Image Display: Showcase multiple images or content slides with automatic or manual navigation, allowing users to engage with the content at their own pace.
  • Customizable Navigation: Offers flexible navigation options such as arrows, dots, or swipe gestures, providing an intuitive user experience across all devices.
  • Responsive Design: Fully responsive and optimized for different screen sizes, ensuring that the carousel looks great on desktop, tablet, and mobile devices.
  • Interactive Thumbnails: Thumbnail previews of slides can be added, enhancing the user’s control over navigating to specific content within the carousel.
Installation

01.Install Dependencies

npm install motion
Source Code
"use client";
import { useState, useEffect, useRef } from 'react';
import { AnimatePresence, motion } from 'motion/react';
import Image from 'next/image';

const Carousel = () => {
  const nextDom = useRef(null);
  const prevDom = useRef(null);
  const carouselDom = useRef(null);
  const sliderDom = useRef(null);
  const thumbnailBorderDom = useRef(null);
  const [index, setIndex] = useState(0);

  // Make the necessary changes to the items array related to content and images as per the need
  const items = [
    { img: '/image/img1.jpg', title: 'Animated Carousel', topic: 'Spark UI', description: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Laudantium, totam! Id iste delectus ratione odit tenetur.' },
    { img: '/image/img2.jpg', title: 'Animated Carousel', topic: 'Spark UI', description: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Laudantium, totam! Id iste delectus ratione odit tenetur.' },
    { img: '/image/img3.jpg', title: 'Animated Carousel', topic: 'Spark UI', description: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Laudantium, totam! Id iste delectus ratione odit tenetur.' },
    { img: '/image/img4.jpg', title: 'Animated Carousel', topic: 'Spark UI', description: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Laudantium, totam! Id iste delectus ratione odit tenetur.' },
  ];

  const handlePrev = () => {
    setIndex(index - 1 < 0 ? items.length - 1 : index - 1);
  };
  
  const handleNext = () => {
    setIndex(index + 1 >= items.length ? 0 : index + 1);
  };

  useEffect(() => {
    let timeAutoNext = 5000;
    let runNextAuto;

    const showSlider = (type) => {
      const sliderItemsDom = sliderDom.current.querySelectorAll('.item');
      const thumbnailItemsDom = thumbnailBorderDom.current.querySelectorAll('.item');

      if (type === 'next') {
        // Append the first item to the end and animate
        sliderDom.current.appendChild(sliderItemsDom[0]);
        thumbnailBorderDom.current.appendChild(thumbnailItemsDom[0]);
        carouselDom.current.classList.add('next');
      } else {
        // Prepend the last item to the start and animate
        sliderDom.current.prepend(sliderItemsDom[sliderItemsDom.length - 1]);
        thumbnailBorderDom.current.prepend(thumbnailItemsDom[thumbnailItemsDom.length - 1]);
        carouselDom.current.classList.add('prev');
      }

      clearTimeout(runNextAuto);
      runNextAuto = setTimeout(() => {
        nextDom.current.click();
      }, timeAutoNext);
    };

    nextDom.current.onclick = () => showSlider('next');
    prevDom.current.onclick = () => showSlider('prev');

    runNextAuto = setTimeout(() => {
      nextDom.current.click();
    }, timeAutoNext);

    return () => {
      clearTimeout(runNextAuto);
    };
  }, [index]);

  return (
    <div className="h-full max-h-[90vh] w-full max-w-[100vw] overflow-hidden relative carousel" ref={carouselDom}>
      <div className="list" ref={sliderDom}>
        <AnimatePresence initial={false} custom={index}>
          <motion.img
            key={index}
            src={items[index].img}
            alt={`Carousel-${index}`}
            className='absolute h-full w-full top-0 object-cover blur-sm brightness-90'
          />
          <div className="absolute top-[20%] max-w-[1080px] w-[80%] left-[45%] -translate-x-[50%] pr-[30%] text-white z-[1]">
            <div className="text-lg md:text-2xl font-bold">{items[index].title}</div>
            <div className="text-3xl md:text-5xl text-yellow-400 font-black">{items[index].topic}</div>
            <div className="my-4">{items[index].description}</div>
            <div className="buttons flex gap-2">
              <button className="text-black text-sm md:text-base rounded-full bg-gray-200 px-8 py-2">Explore</button>
            </div>
          </div>
        </AnimatePresence>
        
        <AnimatePresence initial={false} custom={index}>
          <motion.div
            className="item h-[90vh] w-full relative flex-shrink-0"
            key={index}
            initial={{ opacity: 0.75, scale: 0.25, y: "75%" }}
            animate={{ opacity: 1, scale: 1, y: 0 }}
            transition={{ duration: 0.5, ease: "linear" }}
          >
            <Image
              src={items[index].img}
              alt={`Carousel ${index}`}
              fill
              style={{ objectFit: 'cover' }}
              className='brightness-50'
            />
          </motion.div>
        </AnimatePresence>
      </div>

      <motion.div
        className="thumbnail absolute w-fit z-50 flex gap-5 bottom-[10%] left-1/2"
        initial={{ x: '150px' }}
        animate={{ x: 0 }}
        transition={{ duration: 0.5, ease: "linear" }}
        ref={thumbnailBorderDom}
      >
        {items.map((item, itemIndex) => (
          <motion.div
            className="item h-28 w-48 relative flex-shrink-0"
            key={itemIndex}
            initial={{ width: 0, opacity: 0 }}
            animate={{ width: '192px', opacity: 1 }}
            transition={{ duration: 0.5, ease: "linear" }}
          >
            <Image
              src={item.img}
              alt={`Thumbnail ${itemIndex}`}
              fill
              style={{ objectFit: 'cover' }}
              className="rounded-md"
            />
          </motion.div>
        ))}
      </motion.div>

      <div className="absolute bottom-[10%] right-2/3 z-[1] flex gap-2.5 items-center">
        <button
          id="prev"
          onClick={handlePrev}
          className="w-10 h-10 rounded-full text-white border border-gray-600 font-bold transition-colors delay-75 hover:text-black hover:bg-white hover:border-none"
          ref={prevDom}
        >
          {`<`}
        </button>
        <button
          id="next"
          onClick={handleNext}
          className="w-10 h-10 rounded-full text-white border border-gray-600 font-bold transition-colors delay-75 hover:text-black hover:bg-white hover:border-none"
          ref={nextDom}
        >
          {`>`}
        </button>
      </div>
    </div>
  );
};

export default Carousel;
sparkui