How to optimize image loading on your website

A website full of images can be a huge bottleneck for website performance. This is how I optimized image loading to accomplish a better user experience.

Having a website full of beautiful images is great and all but can be a huge bottleneck for page loading. I often see websites that load several megabytes worth of images just to have a slider on their homepage. Imagine yourself on a slow 3G cellular connection loading that website. That would take ages to load and results in users leaving your website. A good way to test this on your current website is by selecting network throttling in Chrome Devtools.


The problem here is that often the site document is already loaded while the images are still loading. This results in empty sections on your page where an image slowly loads in. Not the thing you want.

In the example below I created a simple website that contains a background image of 4.8MB. As you can see the DOM is loaded in 1.14 seconds. So basically the user sees the content after 1.14 seconds. Pretty good for a 3G network. However, the background image takes 27.32 seconds to load where the user sees parts of the images loading in. The user might already have left your website at this time.

A simple bad optimized website with a background image of 4.8MB over a fast 3G cellular network

It seems that not only your user experience is dropping because of this. In 2010 Google stated that page speed is a factor in their ranking algorithm. My expectation is that this has become a more and more important factor over the years. Google seems to invest a lot in letting developers know about page performance in their conferences.


So how do we overcome this issue? Well, the first thing we can do is to compress the background image by using various tools on the internet. This is an easy win and will reduce the load time to around ten seconds. This seems like a huge step but ten seconds is still way too much.

The next step would be to load a so-called ‘placeholder’ image before we actually load the original image. This ‘placeholder’ is a low-resolution variant of the original image. When we create this image we have reduced the resolution of the image from 7372x4392 pixels to 20x11 pixels. This results in an image size from 4.8MB to 900 bytes.

This reduction in size results in a load time of 550 milliseconds instead of the 10 seconds. But now we have a low-resolution blurred image as our background. This is perfect for the first seconds the page is loading but we want to give the user the great experience of our original background image.

To do so we want to first load in the low-resolution image and already load the high-resolution image asynchronous in the background. Once the high-resolution image is loaded we want to change the low resolution with the high -resolution image.

To accomplish this I used the following javascript I loaded in before the end body tag. This way our script is not render-blocking our page content.

(() => {
'use strict';
// Page is loaded
const objects = document.getElementsByClassName('asyncImage');
  Array.from(objects).map((item) => {
// Start loading image
const img = new Image();
img.src = item.dataset.src;
// Once image is loaded replace the src of the HTML element
img.onload = () => {
return item.nodeName === 'IMG' ?
item.src = item.dataset.src : = `url(${item.dataset.src})`;

The javascript function scans the DOM for any ‘asyncImage’ class. After that, it will load all images that are provided in the data-src attribute on these elements. Once an image is loaded it will replace either the source of the image tag or the background image of not an IMG element.

<div class="asyncImage" data-src="/images/background.jpg">


<img class="asyncImage" src="/images/background-min.jpg" data-src="/images/background.jpg" alt="Beautiful landscape sunrise">

Because the script removes the class of the element once the image has been changed, we can do some awesome CSS transitions if we want to. For example, an ease-in-out transition what will result in a fade once the image get’s replaced.


So what did we do? We improved our user experience, made our website load faster, made it more accessible for users without a fast connection and possibly improved our ranking in Google. That’s a big improvement for such a small change.

The new situation where we first load a low resolution image and then replace it with the original image

As you can see we load a placeholder image in 570ms. Once this is loaded the user sees the blurred low-resolution version of the original image. Once the original image is loaded it will replace the low-resolution image.

We don’t have any weird image rendering issues anymore and we give the user a fast first paint.

See a working example here

Lazy load images

When you want to improve your image loading process even further you might want to consider to lazy load your images.

Lazy loading is a technique where images not direct in the viewport of a user are not loaded. Once the image gets near the border of the viewport the image is loaded.

The benefit of this is fewer bytes to load on initial page load. Often not all images are necessary to be displayed in the viewport of the user. Once the user starts scrolling we need more and more content that can be loaded in. A good approach to implement this behavior is to take a look at the Intersection Observer.

I hope you enjoyed reading this article and are excited to implement this improvement yourself :). Some claps would mean a lot.