Improve your user experience — load images you don’t need lazily

Performance should be a first-class citizen of every modern application.

There are plenty of reasons to justify putting performance at the forefront of your application, and this blog post isn’t about that.

This post is meant to discuss a high-level concept that allows you to gain massive speed improvements and, depending on the platform that you’re using, relatively minimal engineering effort.

Lazy loading is a design pattern commonly used in computer programming to defer initialization of an object until the point at which it is needed.
Wikipedia

In various areas of software engineering, lazy loading is an optimisation step that is particularly beneficial when data is expensive to render, or otherwise process, so you only load the data when the you, or the person using your software, needs it.

This particular concept is easy enough to grasp, but what you can miss is the correct time in which to load data.

On the web, images are wonderful and essential pieces of content, but have usually much larger file sizes than traditional text.

If you load all your images during the rendering phase of your web page, then each image will contribute to the increased loading time of the page. The slower your web page is, the more likely a user is to leave prematurely, and you may be excluding those who don’t have a particularly fast or stable connection from your audience.

This problem is even more obvious on mobile devices, where the viewports are much smaller than on larger devices. This means that when users visit your web page, your images may be below the visible viewport. If that user doesn’t even scroll, then you’ve loaded a relatively large file and given your user a worse experience for nothing.

This issue can compound; if your images aren’t correctly optimised for the viewport the user is currently using, then you may be loading files that are way to large.

The above two problems mean that you could be loading unnecessarily large images that the user never even sees!

My team at carwow has been dealing with this issue on some of our key, high-traffic web pages, and we’ve used the following tools and techniques to help us:

  • Lighthouse to generate a performance report for your web page
  • If there are problems with images, use the IntersectionObserver to render an image only when a user’s viewport intersects with an element
  • For those images, make sure you are loading the correctly sized image for that viewport

Intersection Observer

The IntersectionObserver is an API that allows you to detect when an element intersects with the user’s viewport — effectively becoming visible — and perform an action.

It’s very straightforward to use API, however it isn’t very well supported (iOS Safari is of particular note), so be sure to polyfill it.

We found that the majority of performance boost came from lazy loading the images via the IntersectionObserver.

If you think about it, it makes sense — if the images aren’t loaded on the initial render (assuming a large number of them are below the visible viewport), then the initial render will be faster, regardless of the size of the images because the images are not requested until needed.

Correctly sizing images

Correctly sizing images, therefore, optimises the request for the image when it is needed. It’s ensuring that the right image size for the viewport is used. I highly recommend using srcset for this.

carwow do this by defining small, medium, and large sizes in our shared design styleguide. These sizes are based on aspect ratio.

When we add any images to our view, dynamically generated or not, we generate a srcset with the three sizes that correspond to small, medium, and large, and leave the browser to decide the best one to pick.

We’ve found that lazy loading and correctly sizing images gave us a serious front-end performance boost, for relatively little work. There is a lot more you can do to optimise a webpage, but for bang-for-the-buck changes — optimising images is high up there.