Add a Responsive Image Carousel to Your React App

Ifeoma Imoh

Sliders and carousels are popular ways to display web content such as testimonials, portfolio media, e-commerce sales, and so on for a variety of reasons, including their ability to moderate space usage on your website and interactively display these types of content, provide a great way to tell a story, do a presentation, and so on.

When using sliders, it is important to ensure that they are user-friendly and responsive on various devices, particularly mobile devices, which are becoming more popular.

We'll see how to use the react-responsive-carousel module to include a responsive and customizable slider to a react app. We will use this package to create a simple image gallery and add some customizations to it.

Here is a link to the demo CodeSandbox.

Setting Up the Project

Create a React application using the following command:

1npx create-react-app carousel-react

Next, let’s add the project dependency:

1npm install react-responsive-carousel

Building a Basic Image Slider

Add the following to your src/App.js file:

1import { Carousel } from "react-responsive-carousel";
2import "react-responsive-carousel/lib/styles/carousel.min.css";
3import "./App.css";
4const images = [
5 "https://res.cloudinary.com/ifeomaimoh/image/upload/v1652345767/demo_image2.jpg",
6 "https://res.cloudinary.com/ifeomaimoh/image/upload/v1652366604/demo_image5.jpg",
7 "https://res.cloudinary.com/ifeomaimoh/image/upload/v1652345874/demo_image1.jpg",
8];
9function App() {
10 return (
11 <div className="box">
12 <Carousel useKeyboardArrows={true}>
13 {images.map((URL, index) => (
14 <div className="slide">
15 <img alt="sample_file" src={URL} key={index} />
16 </div>
17 ))}
18 </Carousel>
19 </div>
20 );
21}
22export default App;

This component is quite simple. Starting at the top, we import the Carousel component and its accompanying CSS styles, followed by our CSS file.

The order of loading styles is important so that our custom styles can override the former when we want to make some changes later.

Since we are building an image slideshow, we stored URLs to some image files in an array. Next, In the App component, we return some JSX, which renders the Carousel component with one prop (we will discuss more available props in the next section) named useKeyboardArrows, whose value is set to true. Setting it to true allows the user to control the slider using the arrow keys on the keyboard, which improves the user experience. Finally, within the Carousel component, we iterate over the images that make up our slide show and render them to the screen.

We'll also need some styles to give the application a nice appearance. Copy the styles in this codeSandbox link to your src/App.css file.

Now we can start our application on http://localhost:3000/ using the following command:

1npm start

With just this basic setup, you see the image slider rendered on the screen, as seen below.

Customizing the Slider

The Carousel component ships with a lot of properties that allow us to tailor the appearance and feel of the slider to the needs of our application.

Before we start customizing the slider, let's look at two diagrams. The first shows the major parts of a typical slider. The second one is a more schematic view of the first, and it includes most of the available customizable properties associated with each part of the slider and their respective data types.

Of course, we will not go through all the available properties; instead, the list below identifies typical customization concerns that one may be interested in:

  • Customizing the slider's control buttons.
  • Customizing the indicators.
  • Customizing the slider's status text.
  • Customizing the slider's animation.

For each item above, where appropriate, we would add the required props to the Carousel component to customize it.

Customizing the Slider's Control Buttons

We would need some icons to serve as the buttons; Let's create an SVG file that would house icons for our next and previous buttons. Create this file using the following command:

1cd src
2touch sprite.svg

Add the following to the file:

1<svg width="0" height="0" class="hidden">
2 <symbol
3 clip-rule="evenodd"
4 fill-rule="evenodd"
5 stroke-linejoin="round"
6 stroke-miterlimit="2"
7 xmlns="http://www.w3.org/2000/svg"
8 viewBox="0 0 24 24"
9 id="right"
10 >
11 <path
12 d="m12.007 2c-5.518 0-9.998 4.48-9.998 9.998 0 5.517 4.48 9.997 9.998 9.997s9.998-4.48 9.998-9.997c0-5.518-4.48-9.998-9.998-9.998zm1.523 6.21s1.502 1.505 3.255 3.259c.147.147.22.339.22.531s-.073.383-.22.53c-1.753 1.754-3.254 3.258-3.254 3.258-.145.145-.335.217-.526.217-.192-.001-.384-.074-.531-.221-.292-.293-.294-.766-.003-1.057l1.977-1.977h-6.693c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h6.693l-1.978-1.979c-.29-.289-.287-.762.006-1.054.147-.147.339-.221.53-.222.19 0 .38.071.524.215z"
13 fill-rule="nonzero"
14 ></path>
15 </symbol>
16 <symbol xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="left">
17 <path
18 d="m12.012 2c5.518 0 9.997 4.48 9.997 9.998 0 5.517-4.479 9.997-9.997 9.997s-9.998-4.48-9.998-9.997c0-5.518 4.48-9.998 9.998-9.998zm-1.523 6.21s-1.502 1.505-3.255 3.259c-.147.147-.22.339-.22.531s.073.383.22.53c1.753 1.754 3.254 3.258 3.254 3.258.145.145.335.217.526.217.192-.001.384-.074.531-.221.292-.293.294-.766.003-1.057l-1.977-1.977h6.693c.414 0 .75-.336.75-.75s-.336-.75-.75-.75h-6.693l1.978-1.979c.29-.289.287-.762-.006-1.054-.147-.147-.339-.221-.53-.222-.19 0-.38.071-.524.215z"
19 fill-rule="nonzero"
20 ></path>
21 </symbol>
22</svg>

Update App.js file to import the sprite.svg file as shown below:

1//...
2import sprite from "./sprite.svg";

Next, let's update the App component to include two new props: renderArrowNext and renderArrowPrev.

1function App() {
2 return (
3 <div className="box">
4 <Carousel
5 showIndicators
6 renderArrowNext={(clickHandler, hasNext) => {
7 return (
8 hasNext && (
9 <button className="nav_btn nav_btn_right" onClick={clickHandler}>
10 <svg>
11 <use xlinkHref={sprite + "#right"}></use>
12 </svg>
13 </button>
14 )
15 );
16 }}
17 renderArrowPrev={(clickHandler, hasNext) => {
18 return (
19 hasNext && (
20 <button onClick={clickHandler} className="nav_btn nav_btn_left">
21 <svg>
22 <use xlinkHref={sprite + "#left"}></use>
23 </svg>
24 </button>
25 )
26 );
27 }}
28 >
29 //...
30 }

Both props receive a function value that accepts three parameters each, but we used two of them here: clickHandler is a callback that will be used to change slides, and the other called hasNext is a boolean that tells us if there is a next slide. Based on these parameters, we simply render a button if there is an available slide. We added some class names for styling and the clickHandler callback function to each button.

If you run your app now, you should see the newly added buttons as shown below:

Customizing the Indicators

We would include a prop called renderIndicator to customize our indicators. Let's update our Carousel component with the following:

1function App() {
2 return (
3 <div className="box">
4 <Carousel
5 showIndicators
6 renderArrowNext={(clickHandler, hasNext) => {
7 //...
8 }}
9 renderArrowPrev={(clickHandler, hasNext) => {
10 //...
11 }}
12 renderIndicator={(clickHandler, isSelected, index) => {
13 return (
14 <li
15 onClick={clickHandler}
16 className={`ind ${isSelected ? "active" : ""}`}
17 key={index}
18 role="button"
19 />
20 );
21 }}
22 >
23 //...
24 }

The renderIndicator prop accepts a function that will be called a certain number of times based on the number of slides in our slideShow. In our case, it will be called three times since we have three images. The function also accepts three parameters, and it simply returns some basic JSX.

If you run your app now, you should see the indicators as shown below:

Customizing the Slider's Status Text

The Carousel's status text helps to give users informative feedback, such as the current point in the slideshow and the available number of slides. To customize the status text, we can use the statusFormatter prop. Let's update our App.js file with the following to include this prop:

1function App() {
2 return (
3 <div className="box">
4 <Carousel
5 renderArrowNext={(clickHandler, hasNext) => {
6 //...
7 }}
8 renderArrowPrev={(clickHandler, hasNext) => {
9 //...
10 }}
11 renderIndicator={(clickHandler, isSelected, index) => {
12 //...
13 }}
14 statusFormatter={(currentItem, total) => {
15 return (
16 <p>
17 image {currentItem} of {total}
18 </p>
19 );
20 }}
21 >
22 //.
23 }

The statusFormatter prop accepts a function that expects two parameters. The first parameter called currentItem is a number representing the active slide, while the total parameter represents the total number of available slides. Here we simply render a p tag with some text that includes these parameters.

Run your app now, and you should see the changes made to the status text as shown below.

Customizing the Slider’s Animation

It is also a common requirement to customize how each slide animates in and out of the page. By default, the Carousel component uses a sliding animation and provides us with several props to customize this behavior, such as swipeAnimationHandler, animationHandler, or stopSwipingHandler. Update your App.js file with the following:

1function App() {
2
3 const rotateAnimationHandler = (props, state) => {
4 const transitionTime = props.transitionTime + "ms";
5 const transitionTimingFunction = "ease-in-out";
6 let slideStyle = {
7 display: "block",
8 minHeight: "100%",
9 transitionTimingFunction: transitionTimingFunction,
10 msTransitionTimingFunction: transitionTimingFunction,
11 MozTransitionTimingFunction: transitionTimingFunction,
12 WebkitTransitionTimingFunction: transitionTimingFunction,
13 OTransitionTimingFunction: transitionTimingFunction,
14 transform: `rotate(0)`,
15 position:
16 state.previousItem === state.selectedItem ? "relative" : "absolute",
17 inset: "0 0 0 0",
18 zIndex: state.previousItem === state.selectedItem ? "1" : "-2",
19 opacity: state.previousItem === state.selectedItem ? "1" : "0",
20 WebkitTransitionDuration: transitionTime,
21 MozTransitionDuration: transitionTime,
22 OTransitionDuration: transitionTime,
23 transitionDuration: transitionTime,
24 msTransitionDuration: transitionTime,
25 };
26 return {
27 slideStyle,
28 selectedStyle: {
29 ...slideStyle,
30 opacity: 1,
31 position: "relative",
32 zIndex: 2,
33 filter: `blur(0)`,
34 },
35 prevStyle: {
36 ...slideStyle,
37 transformOrigin: " 0 100%",
38 transform: `rotate(${
39 state.previousItem > state.selectedItem ? "-45deg" : "45deg"
40 })`,
41 opacity: "0",
42 filter: `blur( ${
43 state.previousItem === state.selectedItem ? "0px" : "5px"
44 })`,
45 },
46 };
47 };
48
49 return (
50 <div className="box">
51 <Carousel
52 renderArrowNext={(clickHandler, hasNext) => {
53 //...
54 }}
55 renderArrowPrev={(clickHandler, hasNext) => {
56 //...
57 }}
58 renderIndicator={(clickHandler, isSelected, index) => {
59 //...
60 }}
61 statusFormatter={(currentItem, total) => {
62 //...
63 }}
64 transitionTime={310}
65 animationHandler={rotateAnimationHandler}
66 swipeable={false}
67 >
68 //...
69 }

In the code above, we included three additional props and set their respective values. We set the transition time for each slide to 310 milliseconds. Next, we defined our animationHandler prop, which expects a string or a function, and we set it to a function called rotateAnimationHandler.

The rotateAnimationHandler function accepts two parameters, both of which are objects. The first one, called props, holds the entire properties the Carousel component accepts. The properties we didn't use maintain their initial values. The second one, called state, holds information concerned with the state of the slides. For clarity, some of the properties in this object include the following properties:

1{
2 cancelClick: boolean,
3 hasMount: boolean,
4 initialized: boolean,
5 isMouseEntered: boolean,
6 itemListStyle: object,
7 itemSize: number,
8 prevStyle: Object,
9 previousItem: number,
10 selectedItem: number.
11 selectedStyle: Object,
12 slideStyle:Object,
13 swipeMovementStarted: Boolean,
14 swiping: Boolean
15 }

The essence of the rotateAnimationHandler function is to return an object that contains the three properties in the object above, whose values are set to Object. These properties are objects holding any CSS properties of our choice. The first is called slideStyle, and it contains the default styles each slide should have. prevStyle defines styles that the previously selected slide should have. Finally, the one called selectedStyle holds the styles for the active slide. Our rotateAnimationHandler function just defines styles that rotate each slide in and out of the page.

By customizing the slider animations as we did, swiping the slider will no longer be supported on mobile. To avoid any weird issues, we also disabled swiping using the swappable prop and set it to false.

Once again, refresh your browser to see the running application as shown below.

Find the complete project here on GitHub.

Conclusion

Carousels are still one of the most popular design elements seen on the web. This guide shows us how to embed a responsive and customizable slider in our webPages.

Ifeoma Imoh

Software Developer

Ifeoma is a software developer and technical content creator in love with all things JavaScript.