Optimize your React’s Carousel with MediaLoader

Matti Bar-Zeev - Mar 23 - - Dev Community

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 as my React carousel component and I'm adding six images to it. Here's the code for that:

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

Enter fullscreen mode Exit fullscreen mode

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:

Now, let's introduce the MediaLoader to assist us with the loading strategy. I'm merely wrapping the Carousel component with the MediaLoader 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:

<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>
Enter fullscreen mode Exit fullscreen mode

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:

Pretty neat, right? You can also check the working demo in the project’s Storybook.

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.

Share your thoughts in the comment :)

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .