How to Create an Animated Carousel in React
Carousels are a common way to present a list of media in a limited space. In this tutorial, we'll take a look at how you can create a Carousel
component in React that can receive a list of images and be animated using CSS transitions. By the end of this tutorial, we'll have the following carousel ready to be used:
How to Call the Carousel
First, we're going to need to see how we want to call the carousel. It'll accept an images
prop through which we can pass an array of images for display:
import { Carousel } from './Carousel'
const App = () => {
const images = [
{
src: '/path/to/img.png',
alt: 'Alternate text for image',
url: '/link-to-page',
caption: 'Image Caption next to CTA'
},
{ ... },
{ ... },
{ ... }
]
return <Carousel images={images} />
}
Each image object within the images
array can have the following four different properties:
src
: The file path to the image.alt
: The alternative text for the image, used as thealt
attribute.url
: The URL to which the user will be directed when clicking the carousel image.caption
: A caption to be displayed on the image.
Building the Carousel
Now that we know how the carousel will work, let's take a look at the component itself. If you haven't done so already, create a new file named Carousel.jsx
and include the following:
export const Carousel = ({ images }) => {
return (
<div className="carousel">
<ul>
{images.map((image, index) =>
<li key={index}>
<a href={image.url}>
<img
src={image.src}
alt={image.alt}
/>
<div className="meta">
<span className="cta">Learn More</span>
<span className="caption">{image.caption}</span>
</div>
</a>
</li>
)}
</ul>
</div>
)
}
This code generates a ul
with a list of images that are passed to the component. It's important to wrap the ul
within a wrapper for proper carousel styling later. Inside each li
element, we need to reuse the image properties in various places:
- Line 7: Use
image.url
for thehref
attribute of the anchor. - Line 9-10: Apply
image.src
andimage.alt
for thesrc
andalt
attributes of the image. - Line 14: Use
image.caption
within the.caption
element.
Here is where you can modify the CTA text if you'd like to use different wording.
Styling the Carousel
Now that we have the necessary DOM elements for the carousel, we need to style it accordingly so it actually looks and behaves like a carousel. For the carousel container, we'll need the following rules:
.carousel {
max-width: 2500px;
width: 100%;
overflow: hidden;
padding: 150px 0;
}
We need to include overflow: hidden
to ensure that the width of the carousel doesn't exceed 100% of the screen.
Optionally, you can choose to add an ::after
element to create a fade-out effect for the images that are farthest from the center. This effect can be achieved using a radial gradient:
.carousel::after {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: radial-gradient(circle, rgba(17,12,17,0) 30%, rgba(17,12,17,1) 90%);
}
If you opt for this effect, make sure you include pointer-events: none
to allow the images to receive click events; otherwise, they'll be blocked by the overlaying element.
Moving forward, the list is displayed as a flex
container, centered on the screen. Since we want to apply a sliding animation when navigating the carousel, we need to add a transition: transform
:
.carousel ul {
margin: 0;
padding: 0;
display: flex;
gap: 20px;
list-style-type: none;
justify-content: center;
transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
}
.carousel ul.even {
transform: translateX(-250px);
}
.carousel img {
width: 500px;
height: 250px;
object-fit: cover; /* Make sure you set this to avoid streching */
display: block;
border-radius: 5px;
}
This is also where we want to adjust the carousel for even number of images. To achieve this, we'll use a helper class called .even
. Given that each image has a width of 500px, we'll need to apply half of that width for the adjustment.
By using half the width of an image for adjustment, we can ensure that the active image remains centered on the screen, regardless of the number of images present. Next, we have the anchor that wraps the entire image along with its caption.
For this, we need to set its position
to relative
and its overflow
to hidden
, as we'll be positioning the CTA and the caption absolutely within this element.
.carousel a {
position: relative;
overflow: hidden;
display: block;
}
This will ensure that whenever the CTA and the caption overflow the image, it'll not be visible. As we navigate through the carousel, we want the CTA and the caption to smoothly slide and fade in from the bottom. To achieve this, add the following code to your CSS file:
.carousel .meta {
position: absolute;
display: flex;
align-items: center;
gap: 10px;
bottom: 20px;
left: 20px;
font-weight: bold;
transition: all .6s cubic-bezier(0.4, 0.0, 0.2, 1);
transform: translateY(75px);
opacity: 0;
}
.carousel ul li.active .meta {
transform: translateY(0);
opacity: 1;
}
.carousel .cta {
background: #FFF;
padding: 10px 15px;
display: inline-block;
color: #222;
border-radius: 5px;
}
.carousel .caption {
color: #FFF;
text-shadow: 0 0 5px #000;
}
The important parts are highlighted. Initially, the .meta
element is offset by 75px and has an opacity
of 0, placing it outside of the visible image area. Because we have overflow: hidden
set on the anchor, it'll not be visible.
The image in the center will always have an .active
class, which resets both the transform
and opacity
properties to their initial values. This brings the CTA and the caption back into view.
Adding State to the Carousel
Now that we have all the CSS details sorted out, let's dive back into coding the actual interactivity. We're going to need two useState
hooks to keep track of the internal state of the carousel. Extend the Carousel
component with the following lines of code:
import { useState } from 'react'
export const Carousel = ({ images }) => {
const even = images.length % 2 === 0
const [activeIndex, setActiveIndex] = useState(Math.floor(images.length / 2))
const [transform, setTransform] = useState(even ? -250 : 0)
return (
<div className="carousel">
<ul
className={even ? 'even' : undefined}
style={{ transform: `translateX(${transform}px)` }}
>
{images.map((image, index) =>
<li
key={index}
className={index === activeIndex ? 'active' : undefined}
>
<a href={image.url}>
<img
src={image.src}
alt={image.alt}
/>
<div className="meta">
<span className="cta">Learn More</span>
<span className="caption">{image.caption}</span>
</div>
</a>
</li>
)}
</ul>
<div className="arrows">
<span
className={activeIndex == 0 ? 'left disabled' : 'left'}
/>
<span
className={activeIndex == images.length - 1 ? 'right disabled' : 'right'}
/>
</div>
</div>
)
}
Let's go through the changes to see what has been added:
- Line 4: We can use the remainder (modulo) operator on
images.length
to calculate if there's an even number of images. This is used for center-aligning the images, adjusting their alignment based on the number present in the carousel. - Line 6: We need to store the index of the currently active image to apply corresponding styles. The initial index will consistently be the midpoint of the array, which we can get using
images.length / 2
. - Line 7: We also need a state variable for tracking the transformation amount. If the number of images is even, we want this value to be adjusted to -250 (half the width of an image); otherwise, it'll be 0.
- Lines 12-13: Both the
even
andtransform
variables can be used to apply specific CSS styles to our carousel. - Lines 33-40: Additionally, we need two navigation arrows that we can use for traversing the carousel. Based on the
activeIndex
state, we can apply a.disabled
class to the icon, indicating that no more items are available in that particular direction.
Styling the Navigation
To style the navigation, we'll need to add some additional CSS to our styles file. For creating arrows in CSS, include the following rules:
.carousel .arrows {
display: flex;
justify-content: center;
gap: 50px;
margin-top: 20px;
user-select: none;
}
.carousel .arrows span {
cursor: pointer;
border: solid #FFF;
border-width: 0 4px 4px 0;
display: inline-block;
padding: 4px;
}
.carousel .arrows span.disabled {
border-color: #555;
cursor: default;
}
.carousel .arrows .left {
transform: rotate(135deg);
}
.carousel .arrows .right {
transform: rotate(-45deg);
}
Here, you'll want to set user-select
to none
, and cursor
to pointer
to ensure that the arrows are unselectable while displaying a pointer cursor, indicating interactivity.
To create arrows in CSS, apply styling to only the bottom and right borders, and introduce a rotation effect. Take a look at how they appear both without and with transformations applied:
Adding the Navigation Functionality
Now, there's one final step remaining: adding the navigation functionality. To achieve this, we need to introduce a new function. Extend the Carousel
component with the following function before the return
statement:
const slide = direction => {
if (direction === 'left' && activeIndex !== 0) {
setActiveIndex(activeIndex - 1)
setTransform(transform + 520)
}
if (direction === 'right' && activeIndex !== images.length - 1) {
setActiveIndex(activeIndex + 1)
setTransform(transform - 520)
}
}
This function is responsible for sliding the carousel in both directions. The direction
parameter can take values of either "left" or "right". Regardless of the direction, we need to update both states:
- Increment or decrement the
activeIndex
count by one, based on the direction. - Update the transform of the carousel by +/-520 (accounting for a 20px gap between images).
Ensure you validate activeIndex
against 0 and images.length
to prevent navigation beyond the available number of images.
This function needs to be called when interacting with the arrows. Add the highlighted parts to Carousel.jsx
:
<div className="arrows">
<span
className={activeIndex == 0 ? 'left disabled' : 'left'}
onClick={() => slide('left')}
/>
<span
className={activeIndex == images.length - 1 ? 'right disabled' : 'right'}
onClick={() => slide('right')}
/>
</div>
In essence, our carousel is now fully functional. However, let's also improve the accessibility by enabling users to navigate by clicking on the surrounding images. Add another click event to the li
that calls another function with the index
of the image:
<li onClick={(event) => slideByImage(event, index)}>
Make sure you pass the event along, as it's required inside the function. The slideByImage
function only needs to internally call the slide
function with the appropriate value:
const slideByImage = (event, index) => {
if (index !== activeIndex) {
event.preventDefault()
slide(index < activeIndex ? 'left' : 'right')
}
}
If the index
doesn't match activeIndex
, it signifies that we've clicked on a neighboring image rather than the focused image. In this case, we need to include event.preventDefault
to prevent triggering the default browser action (navigating to the anchor URL).
Based on whether the index
is less or greater than activeIndex
, we can determine whether to call the slide
function with a "left" or "right" value.
Summary
Congratulations! You've just completed the creation of your very first CSS-animated carousel in React! Interested in elevating this project even further? Here are some ideas to enhance the configurability of this carousel:
- Introduce infinite scrolling
- Allow navigation through swipes and keyboard interactions
- Display dots instead of arrows for navigation
Are you looking for more React projects? Check out our React projects roadmap, a comprehensive guide that offers inspiration on what to build next and how to approach it. Thank you for reading through, happy coding! π
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: