Building a Sleek and Animated Image Carousel with React and Framer Motion
Image carousels, also known as slideshows or sliders, are a popular feature found on many websites that allow you to display multiple images within a confined space. Rather than taking up precious real estate by showing all the images at once, a carousel rotates through the images one at a time, usually with nice animated transitions in between.
Not only do carousels look great, but they also provide an engaging way for users to browse through visual content without being overwhelmed. When done well, an image carousel can be an effective tool for showcasing products, portfolios, or storytelling through photos.
In this tutorial, I‘ll show you how to build your own image carousel component in React that has smooth, eye-catching animations powered by the Framer Motion library. By the end, you‘ll have a professional-quality, responsive carousel that you can easily integrate into your React projects. Let‘s get started!
What We‘ll Be Building
Here‘s a preview of the image carousel we‘ll be creating:
[Animated GIF of final carousel]As you can see, it has the following features:
- Displays one image at a time, filling the available space
- Smoothly animates between images with sliding transitions
- Right/left navigation arrows to go to the next/previous image
- Pagination dots that allow jumping to any image
- Fully responsive design that adapts to different screen sizes
We‘ll be using React to build the carousel component itself, and Framer Motion to add the polished animations. If you‘re new to either of these, don‘t worry – I‘ll explain the key concepts as we go.
Setting Up the Project
Before we start building the carousel, we need to set up a new React project. The quickest way to do this is using Create React App:
npx create-react-app react-image-carousel
cd react-image-carousel
This will generate a basic React project for us with all the necessary configuration. Next, we need to install Framer Motion:
npm install framer-motion
Framer Motion is a powerful animation library that makes it easy to add fluid animations and gestures to React components. It uses a declarative syntax, similar to CSS, to define how elements should animate.
With our dependencies installed, we‘re ready to start building the carousel component itself. Open up the project in your favorite code editor and let‘s dive in!
Creating the Carousel Component
Inside the src
directory, create a new file called Carousel.js
. This is where we‘ll write the code for our carousel component. Start by importing React, the useState
hook which we‘ll use to manage state, and the motion
and AnimatePresence
components from Framer Motion:
import React, { useState } from ‘react‘;
import { motion, AnimatePresence } from ‘framer-motion‘;
Next, set up a basic functional component called Carousel
that accepts an array of image URLs as a prop:
const Carousel = ({ images }) => {
const [currentIndex, setCurrentIndex] = useState(0);
return (
// JSX goes here
);
};
export default Carousel;
We‘re using the useState
hook to create a state variable called currentIndex
, which will keep track of which image is currently being displayed. We initialize it to 0
, meaning the first image in the array will be shown by default.
Now, in the component‘s return
statement, let‘s render the current image along with "previous" and "next" buttons for navigation:
return (
<div className="carousel">
<img src={images[currentIndex]} alt="carousel image" />
<div className="carousel-nav">
<button onClick={handlePrevious}>←</button>
<button onClick={handleNext}>→</button>
</div>
</div>
);
To determine which image to display, we use images[currentIndex]
to grab the image URL at the specified index in the images
array.
For the navigation buttons, we‘ve attached onClick
handlers called handlePrevious
and handleNext
, which will be responsible for updating currentIndex
to cycle through the images. Let‘s implement those next:
const handlePrevious = () => {
setCurrentIndex((prevIndex) =>
prevIndex === 0 ? images.length - 1 : prevIndex - 1
);
};
const handleNext = () => {
setCurrentIndex((prevIndex) =>
prevIndex === images.length - 1 ? 0 : prevIndex + 1
);
};
Both of these handler functions use the setCurrentIndex
function to update currentIndex
based on its previous value.
The handlePrevious
function checks if currentIndex
is currently 0
, meaning we‘re on the first image. If so, it "wraps around" to the last image in the array by setting currentIndex
to images.length - 1
. Otherwise, it simply decrements currentIndex
by 1 to go back one image.
Similarly, handleNext
increments currentIndex
by 1 to advance to the next image, unless we‘re currently on the last image, in which case it wraps back around to the first one.
With this logic in place, we can now cycle through the images in both directions using the nav buttons! The only problem is, the transition between images is very abrupt. This is where Framer Motion comes in to save the day.
Animating with Framer Motion
To add smooth animations to our carousel, we‘re going to use Framer Motion‘s AnimatePresence
and motion
components.
First, import the css
function at the top of the file, which we‘ll use to define animation styles:
import { motion, AnimatePresence, css } from ‘framer-motion‘;
Then, update the img
element to use the motion
component and wrap it with AnimatePresence
:
<AnimatePresence>
<motion.img
key={images[currentIndex]}
src={images[currentIndex]}
alt="carousel image"
/>
</AnimatePresence>
The AnimatePresence
component allows components to animate out when they‘re removed from the tree, which is perfect for transitioning between images in our carousel.
We‘ve also added a key
prop to the motion.img
, set to the URL of the current image. This is important because it lets Framer Motion know when the image has changed so it can trigger an animation.
Now, let‘s define the actual animations using variants, which are objects that specify target values for animatable properties. Add this code above the return
statement:
const variants = {
initial: (direction) => ({
x: direction > 0 ? 1000 : -1000,
opacity: 0,
}),
animate: {
x: 0,
opacity: 1,
transition: {
x: { type: ‘spring‘, stiffness: 300, damping: 30 },
opacity: { duration: 0.2 },
},
},
exit: (direction) => ({
x: direction > 0 ? -1000 : 1000,
opacity: 0,
transition: {
x: { type: ‘spring‘, stiffness: 300, damping: 30 },
opacity: { duration: 0.2 },
},
}),
};
There‘s a lot going on here, so let‘s break it down:
- We define 3 variant states:
initial
,animate
, andexit
- Both
initial
andexit
are functions that return different x offsets based on thedirection
parameter (more on this in a bit). This allows us to configure the direction the image slides in from and out to. - In the
animate
state, we setx
to 0 andopacity
to 1, meaning the image will be centered and fully opaque. - We specify transition options for
x
andopacity
to customize the animation curves. Here we‘re using a spring curve for the position and a simple duration for the opacity fade.
In order to determine the direction the image should slide in from, we need to keep track of which nav button was clicked. To do this, add a new state variable called direction
and update the nav button handlers:
const [direction, setDirection] = useState(0);
const handlePrevious = () => {
setCurrentIndex((prevIndex) =>
prevIndex === 0 ? images.length - 1 : prevIndex - 1
);
setDirection(-1);
};
const handleNext = () => {
setCurrentIndex((prevIndex) =>
prevIndex === images.length - 1 ? 0 : prevIndex + 1
);
setDirection(1);
};
We initialize direction
to 0
, and then in handlePrevious
we set it to -1
and in handleNext
we set it to 1
. This value then gets passed into our variant functions to conditionally set the initial and exit offsets.
Finally, apply the variants to the motion.img
:
<AnimatePresence initial={false} custom={direction}>
<motion.img
key={images[currentIndex]}
src={images[currentIndex]}
alt="carousel image"
variants={variants}
initial="initial"
animate="animate"
exit="exit"
custom={direction}
/>
</AnimatePresence>
Note how we pass custom={direction}
both to the AnimatePresence
component as well as the motion.img
. This is how we feed the direction value to the variants.
And with that, our image transitions should now be fully animated! The images will slide in from the correct direction based on which button was clicked and gracefully slide out in the opposite direction.
But we‘re not done yet – let‘s add one more feature to really take this carousel to the next level.
Adding Pagination Dots
It‘s common for image carousels to include pagination dots that allow users to quickly jump to a specific image without having to cycle through them one at a time. Here‘s how we can add this functionality to our carousel:
First, render a set of dots below the image, one for each image in the images
array:
<div className="carousel-dots">
{images.map((_, index) => (
<button
key={index}
onClick={() => handleDotClick(index)}
className={currentIndex === index ? ‘active‘ : ‘‘}
/>
))}
</div>
We map over the images
array and for each one render a button
element. The onClick
handler will call a new handleDotClick
function, passing it the current index. We also conditionally apply an active
class if the dot‘s index matches the currentIndex
.
Next, let‘s write the handleDotClick
function:
const handleDotClick = (index) => {
setCurrentIndex(index);
setDirection(index > currentIndex ? 1 : -1);
};
This function does two things:
- Sets
currentIndex
to the clicked dot‘s index - Sets
direction
based on whether we‘re jumping forward or backward in the image list, similar to the nav button handlers.
Now, when a pagination dot is clicked, the carousel will smoothly animate to the corresponding image! The dots also highlight the currently active image, providing a clear visual indicator of where you are in the list.
Styling with CSS
At this point, our carousel is fully functional, but it doesn‘t look very pretty. Let‘s add some CSS to clean it up.
Create a new file called Carousel.css
and import it at the top of Carousel.js
:
import ‘./Carousel.css‘;
Then, add the following styles:
.carousel {
position: relative;
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.carousel img {
width: 100%;
height: auto;
}
.carousel-nav {
position: absolute;
top: 50%;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
z-index: 1;
}
.carousel-nav button {
background: rgba(0, 0, 0, 0.5);
border: none;
color: white;
font-size: 2rem;
padding: 0.5rem;
cursor: pointer;
transition: opacity 0.3s;
}
.carousel-nav button:hover {
opacity: 0.8;
}
.carousel-dots {
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
display: flex;
justify-content: center;
z-index: 1;
}
.carousel-dots button {
background: rgba(255, 255, 255, 0.5);
border: none;
border-radius: 50%;
width: 12px;
height: 12px;
margin: 0 0.5rem;
cursor: pointer;
transition: background 0.3s;
}
.carousel-dots button.active {
background: white;
}
These styles do the following:
- Constrain the carousel to a max width and center it horizontally
- Make the image fill the carousel container while maintaining aspect ratio
- Position the nav buttons on either side of the image and style them
- Position the dots below the image and style them
- Add hover/active states for the buttons
With these finishing touches, our carousel is now looking much more polished and professional!
Accessibility Considerations
As with any component, it‘s important to ensure our carousel is accessible to all users, including those using assistive technologies. While a full accessibility audit is beyond the scope of this tutorial, here are a few key things to consider:
- Add
alt
text to the images to describe their content - Ensure the nav and pagination buttons are keyboard-focusable and operable
- Provide aria labels for the buttons to clarify their purpose
- Consider adding touch/swipe support for mobile users
Many of these are relatively easy to implement and go a long way towards making the carousel more inclusive.
Conclusion
And there you have it – a complete image carousel component built with React and Framer Motion! We covered a lot of ground in this tutorial, including:
- Setting up a new React project with Create React App
- Installing and configuring Framer Motion
- Creating a reusable Carousel component
- Rendering a list of images and cycling through them
- Adding navigation buttons and pagination dots
- Animating the image transitions with Framer Motion variants
- Styling the carousel with CSS
- Discussing accessibility best practices
I encourage you to experiment with the code and make the carousel your own. Try adding different animation effects, customizing the styling, or even extending it with additional features like autoplay or infinite looping.
The complete source code for this tutorial is available on GitHub. Feel free to use it as a starting point for your own projects!
If you have any questions or feedback, reach out in the comments below. Happy coding!