- Published on
Creating a Responsive Image Carousel in Next.js: A Step-by-Step Guide
- Authors
- Name
- Stackademic Blog
- @StackademicHQ
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.
- 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="relative h-60 w-80 overflow-hidden rounded-md">
<div
ref={carouselRef}
style={{
left: -currentImg * carouselSize.width,
}}
className="absolute flex h-full w-full transition-all duration-300"
>
{data.map((v, i) => (
<div key={i} className="relative h-full w-full shrink-0">
<Image className="pointer-events-none" alt="random image" fill src={v.image} />
</div>
))}
</div>
</div>
<div className="mt-3 flex justify-center">
<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)
andh-60 (240px)
) and is set tooverflow-hidden
to hide any content beyond its dimensions. - The
ref
attribute is used to referencecarouselRef
for getting the dimensions, andstyle
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 theImage
component from 'next/image'. - Finally, navigation buttons (
<
and>
) are provided to move to the previous and next images by incrementing/decrementing the currentImageIndex. Thedisabled
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="relative h-60 w-80 overflow-hidden rounded-md">
{/* Image container */}
<div
ref={carouselRef}
style={{
left: -currentImg * carouselSize.width,
}}
className="absolute flex h-full w-full transition-all duration-300"
>
{/* Map through data to render images */}
{data.map((v, i) => (
<div key={i} className="relative h-full w-full shrink-0">
<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="mt-3 flex justify-center">
<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="flex min-h-screen flex-col items-center justify-center text-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
Now you can run the Next JS app using http://localhost:3000/ npm run dev
Visit 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.