Creating a Responsive Image Carousel in Next.js: A Step-by-Step Guide

thumbnail

Hello Guys, In this quick tutorial, we’ll explore how to create a simple yet cool image carousel using Next.js. If you’re new to React/Next JS or looking to enhance your skills, this will be an excellent project to get started with. By the end of this tutorial, you’ll have a good understanding of how to create a carousel component in Next.js.

Let’s dive in and create a new NEXT JS project.

npx create-next-app@latest

Carousel Component

Create a new component file, let's call it carousel.tsx inside the components folder.

  1. Imports

    // src/components/carousel.tsx

    "use client" import Image from 'next/image' import React, { useEffect, useRef, useState } from 'react'

Here, we import the necessary dependencies for our carousel component:

  • Image from 'next/image': A Next.js component for optimizing and serving images.
  • useEffect, useRef, useState: React hooks for handling side effects, managing refs, and states.

2. Initialization

...
const Carousel = ({ data }: {
    data: {
        image: string
    }[]
}) => {
  const [currentImg, setCurrentImg] = useState(0)
  const [carouselSize, setCarouselSize] = useState({ width: 0, height: 0 })
  const carouselRef = useRef(null)
  return <div></div>
}

The Carousel component takes a prop data, which is an array of objects. Each object in the array has a image property, representing the URL of an image. Below are the state, and ref initialization

  • currentImg: Keeps track of the index of the currently displayed image.
  • carouselSize: Stores the dimensions of the carousel container.
  • carouselRef: Ref used to get the dimensions of the carousel container.

3. useEffect for the extracting initial Size

...
useEffect(() => {
    let elem = carouselRef.current as unknown as HTMLDivElement
    let { width, height } = elem.getBoundingClientRect()
    if (carouselRef.current) {
        setCarouselSize({
            width,
            height,
        })
    }
}, [])
...

The useEffect hook runs after the initial render. It calculates and sets the dimensions of the carousel container using the getBoundingClientRect method. This ensures that the carousel knows its size for proper functionality.

4. Carousel Structure

...
return (
    <div>
        <div className='w-80 h-60 rounded-md overflow-hidden relative'>
            <div ref={carouselRef}
                style={{
                    left: -currentImg * carouselSize.width
                }}
                className='w-full h-full absolute flex transition-all duration-300'>
                {data.map((v, i) => (
                    <div key={i} className='relative shrink-0 w-full h-full'>
                        <Image
                            className='pointer-events-none'
                            alt="random image"
                            fill
                            src={v.image}
                        />
                    </div>
                ))}
            </div>
        </div>
        <div className='flex justify-center mt-3'>
            <button
                disabled={currentImg == 0}
                onClick={() => setCurrentImg(prev => prev - 1)}
                className={`border px-4 py-2 font-bold ${currentImg == 0 && 'opacity-50'}`}
            >
                {"<"}
            </button>
            <button
                disabled={currentImg == data.length - 1}
                className={`border px-4 py-2 font-bold ${currentImg == data.length - 1 && 'opacity-50'}`}
                onClick={() => setCurrentImg(prev => prev + 1)}
            >
                {">"}
            </button>
        </div>
    </div>
)
  • The outer div contains the entire carousel component.
  • The first inner div represents the carousel container. It has a fixed size (w-80 (320px)and h-60 (240px)) and is set to overflow-hidden to hide any content beyond its dimensions.
  • The ref attribute is used to reference carouselRef for getting the dimensions, and style is dynamically adjusted based on the current image index for smooth transitions. It is positioned to absolute, and images are lined up in a row flex container, such that the left value will be changed dynamically to show the active image and value for it calculated by multiplying -current image index and carousel width such that if our width of the carousel/image is 100px and we have 4 images so total 400 pixels initial left value is 0 (which means we are on first image) to move to next image we need move container left by 100px since we need to move left so we are adding minus for calculation, similarly if it want to move to 3rd image then left style should be -2 * 100px so the formula is — currentImageIndex* carouselWidth where currentImageIndex value ranges from 0 to carousel data array length — 1. transition and duration style helps in smoothing out the changes which gives slide effect.
  • The data.map iterates over the array of images, rendering each image with the Image component from 'next/image'.
  • Finally, navigation buttons (< and >) are provided to move to the previous and next images by incrementing/decrementing the currentImageIndex. The disabled attribute is used to prevent moving beyond the limits, so when we are currently at 1st image then left button is disabled, and when at the last image right button is disabled.

Final Code:

// carousel.tsx
"use client"
import Image from 'next/image'
import React, { useEffect, useRef, useState } from 'react'

const Carousel = ({ data }: {
    data: {
        image: string
    }[]
}) => {
    // State and Ref initialization
    const [currentImg, setCurrentImg] = useState(0)
    const [carouselSize, setCarouselSize] = useState({ width: 0, height: 0 })
    const carouselRef = useRef(null)

    // useEffect to get the initial carousel size
    useEffect(() => {
        let elem = carouselRef.current as unknown as HTMLDivElement
        let { width, height } = elem.getBoundingClientRect()
        if (carouselRef.current) {
            setCarouselSize({
                width,
                height,
            })
        }
    }, [])

    return (
        <div>
            {/* Carousel container */}
            <div className='w-80 h-60 rounded-md overflow-hidden relative'>
                {/* Image container */}
                <div
                    ref={carouselRef}
                    style={{
                        left: -currentImg * carouselSize.width
                    }}
                    className='w-full h-full absolute flex transition-all duration-300'>
                    {/* Map through data to render images */}
                    {data.map((v, i) => (
                        <div key={i} className='relative shrink-0 w-full h-full'>
                            <Image
                                className='pointer-events-none'
                                alt={`carousel-image-${i}`}
                                fill
                                src={v.image || "https://random.imagecdn.app/500/500"}
                            />
                        </div>
                    ))}
                </div>
            </div>

            {/* Navigation buttons */}
            <div className='flex justify-center mt-3'>
                <button
                    disabled={currentImg === 0}
                    onClick={() => setCurrentImg(prev => prev - 1)}
                    className={`border px-4 py-2 font-bold ${currentImg === 0 && 'opacity-50'}`}
                >
                    {"<"}
                </button>
                <button
                    disabled={currentImg === data.length - 1}
                    onClick={() => setCurrentImg(prev => prev + 1)}
                    className={`border px-4 py-2 font-bold ${currentImg === data.length - 1 && 'opacity-50'}`}
                >
                    {">"}
                </button>
            </div>
        </div>
    )
}

export default Carousel

Next, we can add this to our home page or the page you wish. For the tutorial purpose, I am using random images from picsum.photos . Putting the URLs in the data variable in the format we need, and importing the Carousel component, and passing the data to it.

import Carousel from '@/components/carousel'
const DATA = [{ image: "https://picsum.photos/seed/random101/500/500" },
 { image: "https://picsum.photos/seed/random102/500/500" }, 
 { image: "https://picsum.photos/seed/random103/500/500" }]
export default function Home() {
  return (
    <main className="min-h-screen flex flex-col text-center justify-center items-center">
      <Carousel data={DATA} />
    </main>
  )
}

Since we are using external images we need to configure next.config.ts with the hostname of the website from where the images are pulled from.

/** @type {import('next').NextConfig} */
const nextConfig = {
    images: {
        remotePatterns: [
            {
                hostname: "picsum.photos"
            }
        ]
    }
}

module.exports = nextConfig

For more details please refer to NEXT JS docs on the image

Components: | Next.js

Now you can run the Next JS app using npm run devVisit http://localhost:3000 in your browser to see the carousel in action.

Congratulations! You've successfully built a responsive image carousel in Next.js. This versatile component can be customized further to meet the specific needs of your projects. Feel free to experiment with additional features, such as automatic sliding, captions, or custom animations, to enhance the user experience.