Optimize your React’s Carousel with MediaLoader
Tags: react, webdev, opensource, performance
Published: 2024-03-23 15:06:33

At some stage in our development journey, we've likely come across an image carousel. This component typically contains multiple images nested within it, allowing users to navigate forwards and backwards between them.

However, there's a performance drawback to many existing implementations. Consider an image carousel featuring 100 images: in most scenarios, all images are loaded upfront, even if the user only views a single image. This results in a significant waste of network bandwidth and may delay the retrieval and availability of other critical assets.

Let me show you what I mean -
I’m using the [react-responsive-carousel](https://github.com/leandrowd/react-responsive-carousel) as my React carousel component and I'm adding six images to it. Here's the code for that:

```javascript
<Carousel showThumbs="{false}" className="carousel">
    <div>
        <img src="/assets/image-01.jpg" />
        <p className="legend">Legend 1</p>
    </div>
    <div>
        <img src="/assets/image-02.jpg" />
        <p className="legend">Legend 2</p>
    </div>
    <div>
        <img src="/assets/image-03.jpg" />
        <p className="legend">Legend 3</p>
    </div>
    <div>
        <img src="/assets/image-04.jpg" />
        <p className="legend">Legend 4</p>
    </div>
    <div>
        <img src="/assets/image-05.jpg" />
        <p className="legend">Legend 5</p>
    </div>
    <div>
        <img src="/assets/image-06.jpg" />
        <p className="legend">Legend 6</p>
    </div>
</Carousel>

``` 

As demonstrated in the video below, upon rendering the carousel, all six images are requested and fetched, even though only the first one is initially visible. Subsequently, I can proceed to navigate between the images:

<video controls src="https://github.com/mbarzeev/pedalboard/raw/master/packages/media-loader/docs/assets/origin-carousel.mp4"/>

Now, let's introduce the [MediaLoader](https://www.npmjs.com/package/@pedalboard/media-loader) to assist us with the loading strategy. I'm merely wrapping the Carousel component with the [MediaLoader](https://www.npmjs.com/package/@pedalboard/media-loader) and specifying a loading strategy that operates as follows: it examines the transform style of the sliding element and loads the necessary image accordingly. This approach is straightforward yet effective for this particular scenario. Take a look at the updated code below:

```javascript
<MediaLoader
    loadingStrategy={(mediaHTMLElementRefs, loadMedia) => {
        const sliderElement = document.querySelector('.slider.animated') as HTMLElement;
        let imageIndexToLoad = getImageIndexFromTransformStyle(sliderElement.style.transform);


        function getImageIndexFromTransformStyle(transformStyle: string) {
            let imageIndexToLoad = 0;
            if (transformStyle) {
                const match = transformStyle.match(/-?[\d.]+(?:%|px)/);
                if (match) {
                    const transformPercent = match[0];
                    imageIndexToLoad = parseInt(transformPercent) / -100;
                }
            }
            return imageIndexToLoad;
        }


        function loadImageByIndex(index: number) {
            let imageRefToLoad = mediaHTMLElementRefs[index];
            if (imageRefToLoad && imageRefToLoad.current) {
                loadMedia(imageRefToLoad.current);
            }
        }


        const callback = (mutationList) => {
            for (const mutation of mutationList) {
                if (mutation.attributeName === 'style') {
                    const style = mutation.target.style;
                    imageIndexToLoad = getImageIndexFromTransformStyle(style.transform);
                    loadImageByIndex(imageIndexToLoad);
                    break;
                }
            }
        };


        const observer = new MutationObserver(callback);
        observer.observe(sliderElement, {attributes: true});


        loadImageByIndex(imageIndexToLoad);
    }}
>
    <Carousel showThumbs={false} className="carousel">
        <div>
            <img src="/assets/image-01.jpg" />
            <p className="legend">Legend 1</p>
        </div>
        <div>
            <img src="/assets/image-02.jpg" />
            <p className="legend">Legend 2</p>
        </div>
        <div>
            <img src="/assets/image-03.jpg" />
            <p className="legend">Legend 3</p>
        </div>
        <div>
            <img src="/assets/image-04.jpg" />
            <p className="legend">Legend 4</p>
        </div>
        <div>
            <img src="/assets/image-05.jpg" />
            <p className="legend">Legend 5</p>
        </div>
        <div>
            <img src="/assets/image-06.jpg" />
            <p className="legend">Legend 6</p>
        </div>
    </Carousel>
</MediaLoader>
```

Notice that there have been no alterations made to the Carousel code itself. As depicted in the video below, only the necessary image is now loaded:

<video controls src="https://github.com/mbarzeev/pedalboard/raw/master/packages/media-loader/docs/assets/medialoader-carousel.mp4"/>

Pretty neat, right? You can also check the working demo in the [project’s Storybook](https://master--65f0a7c612ec612e3b7b2059.chromatic.com/?path=/docs/medialoader--docs).

Naturally, you can refine the logic and make it more sophisticated. For instance, you could consider loading the next image upon hovering over the arrow button. Additionally, you might opt to load images exclusively during periods of network idleness. The decision is entirely yours, granting you full control over the process.

To read more about the MediaLoader see [this post](https://dev.to/mbarzeev/take-control-over-your-media-loading-kjl).

Share your thoughts in the comment :)