Progressive Image Loading with React Hooks

Posted on

 React JavaScript

Improve the perceived load times of your large images.

Last week I was building a web page for our e-commerce site, the design had a really beautiful large hero image that took up half the width of the screen. After implementing the design I noticed that on slower internet connections this beautiful large image loaded very slowly.

The designer intended any user’s first impression of this page to be Wow, look at that beautiful imagery. Unfortunately as the image was so large they were instead presented with a bland page while it loaded.

An example of the problem

Click the button below to load the image

Hopefully you can see that the image loads quite slowly. If not then… congratulations on your super-fast internet connection! Hopefully you can still follow along by imagining it was slow to load.

How to solve this problem?

Obviously I couldn’t let this fly. I wanted to come up with a reusable solution that allowed me to show a representation of the image while it loaded, as this would let me stick to the designer’s initial vision.

There is a technique commonly known as “Blurring Up” images. This involves initially rendering a very small image, scaling it up and then applying a Gaussian Blur. This method is what I settled on using, and I think the result is pretty good.

Below is an example of the finished product. Compare it to the slow version above, what do you think?

Click the button below to load the image

I think it works very nicely. One of the most important things for me when implementing this was that I wanted it to be reusable, as I wanted to easily add this effect whenever I was dealing with a large image. I also wanted it to work for both <img /> tags and background images, as we use a mixture of these depending on the context.

The solution

Whenever I am working in React and want something reusable I think “Could I make this a hook?”. Check out the code below.

import React from 'react';
const useProgressiveImg = (lowQualitySrc, highQualitySrc) => {
const [src, setSrc] = React.useState(lowQualitySrc);
React.useEffect(() => {
setSrc(lowQualitySrc);
const img = new Image();
img.src = highQualitySrc;
img.onload = () => {
setSrc(highQualitySrc);
};
}, [lowQualitySrc, highQualitySrc]);
return [src, { blur: src === lowQualitySrc }];
};
export default useProgressiveImg;

This is a hook which accepts two arguments: a low-quality image src, and a high-quality image src.

By default this hook will return the src of the low-quality pixelated image. Once the higher quality image has loaded, it will return that instead. This is achieved by adding an event listener to a new Image object. The src attribute of this Image is set to the src of our high quality image, so the event listener fires once the full-size image has loaded.

Once that image has loaded we switch from returning the pixelated image to returning the full-size image.

You may have also noticed that this hook returns a second value, which is an object containing a value I have called blur. We can use this to know whether or not we need to “blur” the image. If the current src being returned is that of the low-quality image, then we should blur the image, as that is the nasty pixelated one.

Usage

Here is an example of how to use this hook to get the desired “blur up” effect.

import React from "react";
import useProgressiveImg from "./useProgressiveImg";
const BlurredUpImage = () => {
const [src, { blur }] = useProgressiveImg(
"./tiny.jpg",
"./large.jpg"
);
return (
<img
src={src}
style={{
width: 200,
filter: blur
? "blur(20px)"
: "none",
transition: blur
? "none"
: "filter 0.3s ease-out"
}}
/>
)
}

In this example tiny.jpg should be a very small version of the full-size image large.jpg.

I have used inline styles here for simplicity’s sake, but you could also use the blur value to toggle a class, or pass it in as a prop to a styled component. The important thing here is that we are adding a CSS blur() filter if the image hasn’t loaded yet. The transition makes the transition between the blurred and non-blurred states nice and smooth.

Thanks for reading!

Let me know if this article was useful to you by using the twitter comment link below.

Comment on this article with Twitter

I am Ben Honeywill. I'm a Front-End Engineer from the UK. I work for @LushLtd. Web development JavaScript

Read more of my articles