How to Create an Animated Carousel in React

How to Create an Animated Carousel in React

Using CSS transitions
Ferenc Almasi • 2023 August 16 • 📖 17 min read
  • twitter
  • facebook
⚛️ 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:

animated carousel in React
The final version of the animated carousel in React
Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

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} />
}
App.jsx Calling the Carousel component
Copied to clipboard!

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 the alt 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.
🔐 Become a member or login to get access to the full source code in one piece. With TypeScript included.

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>
    )
}
Carousel.jsx Create the base of the carousel
Copied to clipboard!

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 the href attribute of the anchor.
  • Line 9-10: Apply image.src and image.alt for the src and alt 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.

Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

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;
}
styles.css Your max-width may differ depending on your use-case
Copied to clipboard!

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%);
}
styles.css You can use cssgradient.io to generate gradients
Copied to clipboard!

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;
}
styles.css Make sure you remove the list-style-type
Copied to clipboard!

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.

Adjusting carousel position based on number of images
Image will always be centered on the viewport if we adjust the carousel by half of the width of an image

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;
}
styles.css Add positioning and overflow to anchor
Copied to clipboard!

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;
}
styles.css Style the CTA and the caption
Copied to clipboard!

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.

CTA and caption animated
CTA and caption animated into view for active image

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>
    )
}
Carousel.jsx Add hooks to the Carousel component
Copied to clipboard!

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 and transform 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.
Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

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);
}
styles.css Style the navigation
Copied to clipboard!

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:

🔐 Become a member or login to get access to the full source code in one piece. With TypeScript included.

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)
    }
}
Carousel.jsx Add the slide functionality
Copied to clipboard!

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>
Carousel.jsx Call the slide function
Copied to clipboard!

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)}>
Carousel.jsx Add click event listener on li
Copied to clipboard!

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')
    }
}
Carousel.jsx Add the slideByImage function
Copied to clipboard!

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.

Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

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! 🎠

React Projects Roadmap
  • twitter
  • facebook
⚛️ React
Did you find this page helpful?
📚 More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Unlimited access to hundreds of tutorials
  • check Access to exclusive interactive lessons
  • check Remove ads to learn without distractions
Become a Pro

Recommended

x