Tutorial Progressive image loading in React

Images play a significant role in enhancing websites, elevating user experiences, and driving higher engagement. Nevertheless, the loading time for high-quality images can be a potential source of frustration, particularly on slower internet connections.

To tackle this issue, developers must implement strategies that ensure a seamless loading experience. One effective approach is progressive image loading.

In this tutorial, we will explore the concept of progressive image loading, delve into its implementation within React, and cover other relevant aspects of this strategy.

Why progressive image loading is useful

Progressive image loading allows developers to show a lower-resolution or preview image while the full-resolution image is loading. This enhances the user experience by giving them a preview and indicating that the full image will appear shortly.

The GIF below demonstrates the use of a native <img /> element to render an image.

Native Img Element Loading On Webpage

As we can see, although the page is already loaded, the image takes an extra second before it’s rendered, thereby causing empty spaces. The experience worsens when we have a very slow internet connection.

By using the progressive loading technique, we can render a tiny file size version of the image to decrease load time. Once the high-resolution version is loaded, we can swap the image files. See the GIF demonstration below:

Clicking Reload Button At Top Left Of Screen To Show Result Of Progressive Image Loading With Onload Event Handler

Since the placeholder image loads almost immediately, this strategy can also help reduce the issue of layout shifts caused by webpage images. Be aware that layout shifts occur mainly because the browser is unaware of how much space to reserve for the image.

We can prevent this behavior by adding width and height attributes to the image. This notifies the browser to reserve a set amount of space for the image to occupy. We must then apply a max-width: 100% and a height: auto on the image in our CSS file to ensure the image behaves correctly in a responsive layout.

In this tutorial, we will learn how to improve the user experience and prevent layout shifts by loading images progressively in React from scratch. We will also learn how to use an external library to achieve the same result.

To follow this tutorial, you must have a working knowledge of React.

Progressive image loading techniques in React

The magic of progressive images is achieved by creating two image versions: the actual image, and a smaller file version, usually less than 2kB in size.

The low-quality image is loaded at first for quick display and scaled up to fit the main image width while the main image downloads. Then, a blur filter and proper CSS transitioning are applied.

React frameworks like Gatsby and Next.js also use this pattern in their image components. But instead of the users manually creating a small version of the image, the frameworks automatically generate it from the source image.

In addition, these frameworks use advanced image processing options and enable the lazy loading of images further down the screen.

In our case, the focus is progressive image loading in React. Let’s get started with the implementation.

Creating an image component

We will create an image component called ProgressiveImg to encapsulate an <img /> element and the logic for the progressive loading. This component can then be used as a replacement for the native <img /> elements.

const ProgressiveImg = ({ placeholderSrc, src, ...props }) => {
  return (
    <img
      {...{ src: placeholderSrc, ...props }}
      alt={props.alt || ""}
      className="image"
    />
  );
};
export default ProgressiveImg;

In the above code, the component receives the actual image source, its placeholder source, and every other prop that we pass. Then, we assigned these props to the <img /> element attributes.

Notice how we used the  spread operator to inject any other props that the component receives. For instance, the component will receive a required image width and height, as we will see in a moment. In the meantime, we assigned a placeholder image source to the src for quick display, as we mentioned earlier.

Next, let’s ensure we pass all the above-mentioned props to the <ProgressiveImg /> like so:

import ProgressiveImg from "./components/ProgressiveImg";
import image from "./images/large_.jpg";
import placeholderSrc from "./images/tiny_.jpg";

export default function App() {
  return (
    // ...
    <ProgressiveImg
      src={image}
      placeholderSrc={placeholderSrc}
      width="700"
      height="465"
    />
    // ...
  );
}

As seen in the code, we passed the image and its small-size version that we resized to be less than 2kB. We must also pass along the image width and height to prevent layout shifts.

Be sure you maintain the aspect ratio if the image size is bigger than the assigned value. With this, the frontend should look like so:

Demonstrating How A Small File Size Image Appears In The Frontend

Updating the thumbnail to the actual image

To update the src of the img and render the actual image, we must store the default image source in a state variable via the useState Hook. Then, we can update the variable inside a useEffect Hook once the actual image loads.

Let’s update the ProgressiveImg component so we have the following:

import { useState, useEffect } from "react";

const ProgressiveImg = ({ placeholderSrc, src, ...props }) => {
  const [imgSrc, setImgSrc] = useState(placeholderSrc || src);

  useEffect(() => {
    // update the image
  }, []);

  return (
    <img
      {...{ src: imgSrc, ...props }}
      alt={props.alt || ""}
      className="image"
    />
  );
};
export default ProgressiveImg;

Notice that the src attribute for the img is now assigned a value of the state variable. By default, this value is set to the placeholder source if we have it. Otherwise, it is assigned the main image.

Next, let’s update the useEffect Hook so we have the following:

useEffect(() => {
  const img = new Image();
  img.src = src;
  img.onload = () => {
    setImgSrc(src);
  };
}, [src]);

In this Hook, we started by creating an img element by instantiating an Image() object and setting the src attribute to the actual image source.

With the onload event handler on the image object, we can detect when the actual image has completely loaded in the background. Then, we update the image src to the actual image.

See the result below:

Clicking Reload Button At Top Left Of Screen To Show Result Of Progressive Image Loading With Onload Event Handler

Implementing a transition blur

Let’s add a CSS transition blur for a smooth effect. In the ProgressiveImg component, add the following code above the return statement:

const customClass =
    placeholderSrc && imgSrc === placeholderSrc ? "loading" : "loaded";

We will dynamically add class names to the image based on the loading status.

So, update the <img /> to include the custom class name:

return (
  <img
    // ...
    className={`image ${customClass}`}
  />
);

If the actual image is still loading, we add a loading class to the image. Otherwise, we add a loaded class. We can then update the CSS to include the style rules:

.loading {
  filter: blur(10px);
  clip-path: inset(0);
}
.loaded {
  filter: blur(0px);
  transition: filter 0.5s linear;
}

Save and see the changes in the frontend. See the complete code on CodeSandbox.

Progressive image loading using a library

We can also load images progressively by using a library called react-progressive-graceful-image. To use it, we must install it:

npm i react-progressive-graceful-image

We can then import the ProgressiveImage component and implement it like so:

import ProgressiveImage from "react-progressive-graceful-image";
import image from "./images/large_.jpg";
import placeholderSrc from "./images/tiny.jpg";

export default function App() {
  return (
    // ...
    <ProgressiveImage src={image} placeholder={placeholderSrc}>
      {(src, loading) => (
        <img
          className={`image${loading ? " loading" : " loaded"}`}
          src={src}
          alt="sea beach"
          width="700"
          height="465"
        />
      )}
    </ProgressiveImage>
    // ...
  );
}

The ProgressiveImage component uses a render props technique to implement progressive image loading. In its children function prop, we have access to the src and loading arguments in the render callback.

With the loading argument, we can dynamically add classes to the img element. The loading returns true while the actual image is loading; otherwise, it returns false.

We have also added style rules for the classes in the CSS file. See the complete code and demo on CodeSandbox.

Conclusion

By incorporating a progressive image loading approach, we can significantly enhance the user experience within our React project.

In this guide, we discussed the process of progressively loading images in React, both with and without the use of an external library. I trust you have gained valuable insights and found this tutorial informative. If you have any questions or would like to make contributions, please feel free to do so in the comments section.

No comments:

Post a Comment