Images can have a great impact on websites. Their presence improves the user experience and helps increase engagement. However, loading a high-quality image can take time and make this experience more frustrating, especially on a slow internet connection.
To address this problem, developers need to deploy a strategy that supports a positive loading experience. One such strategy is progressive image loading.
In this tutorial, we will learn about progressive image loading, how to deploy this strategy in React, and more.
With progressive image loading, the developer can display a low-resolution or preview image until the actual image loads. This improves the user experience by providing awareness that images are coming up momentarily.
The GIF below demonstrates the use of a native <img />
element to render an image.
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:
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.
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.
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:
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:
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.
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.
By implementing a progressive image loading technique, we can greatly improve the user experience in our React project.
In this tutorial, we covered how to load images progressively in React with and without an external library. I hope you’ve learned a lot and enjoyed reading this guide. If you have questions or contributions, I’m in the comment section.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
One Reply to "Progressive image loading in React: Tutorial"
This is a very cool tutorial, thanks for writing it up!