Loading Background Images Asynchronously with Lazy Load

Lazy loading images is a standard strategy for improving web page performance and user experience. By lazy loading images, a placeholder will be displayed first, and once the element enters user’s viewport, the actual image will be loaded and the placeholder is swapped for the actual image.

Depending on how many images are being loaded outside user’s viewport, this strategy can significantly improve page load time. It is also one of the Google recommended optimization tips for all websites.

Why Background Images?

In today’s article we will show a simple example how to lazy load background images. Background images can be tricky because most lazy loading libraries are designed to work with <img/> (or picture) tags.

In case you are working with image tags, there are already plenty of great, reusable implementations for various platforms already out there. One might even ask,

Why do you want to load images as backgrounds, why not just switch to using image tags?

The difference comes from the fact that, with background images, you can set CSS properties such as background-size, background-repeat and background-position. These come in especially handy when trying to design consistent looking list views, as is often the case in scenarios where you need to lazy load images in the first place. (You can do this also with newer HTML tags by combining <picture>, <image>, and object-fit property, but that is a story for another article!)

Considerations

Next we will show you a simple example of how to implement this behavior. This example is purposely simplified and you are encouraged to add your own flair to it.

Another point to consider is that this example is designed for pages that load their entire HTML DOM tree up front. If you are using a framework (such as react, vue, or angular) to dynamically load page content, this solution is not a good option for you.

This sample implementation will load an image one time and the image will be visible from that point forward.


Sample Code

This example involves working with 3 files:

  • index.html - HTML template with elements
  • style.css - CSS stylesheet
  • script.js - javascript file

This implementation is an adapted version of this example.

index.html

1
2
3
4
5
6
7
<!-- other elements here -->

<div
style="background-image:url(../image.jpg)"
class="image lazy-background"></div>

<!-- more elements here -->

For each element with a background image, add a class name that indicates the background will be lazy loaded. You can choose any class name; in this example we use lazy-background.

style.css

1
2
3
4
5
6
7
8
9
10
11
.image{
width: 300px;
height: 150px;
background-color: #eee;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.image.lazy-background{
background-image: none!important;
}

Use CSS stylesheet to set the element background and other properties for when the special class name lazy-background is applied, as well as when image is in the viewport. The element is in the viewport if lazy-background class name is NOT applied.

Feel free to change this CSS to match your style, BUT make sure to disable background-image when element has lazy-background class applied to it. Also note you have to use important to override the inline style attribute.

Next add a reference to your stylesheet to your index.html. When you load the page, you should see the placeholder state and no images.

script.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
document.addEventListener("DOMContentLoaded", function() {

// use query selector that matches your class name
// you may assign it to a variable; we have used literal here
// to make this example more clear.

const lazyBackgrounds = [].slice.call(
document.querySelectorAll(".lazy-background"));

if ("IntersectionObserver" in window) {
let lazyBackgroundObserver =
new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {

// remove the lazy class name
// when image enters viewport
entry.target.classList.remove("lazy-background");
lazyBackgroundObserver.unobserve(entry.target);
}
});
});

lazyBackgrounds.forEach(function(lazyBackground) {
lazyBackgroundObserver.observe(lazyBackground);
});
} else {
lazyBackgrounds.forEach(function(entry) {
entry.classList.remove("lazy-background");
});
}
});

The above code snippet is responsible for detecting when an element enters the viewport. When this event occurs, the lazy-background class will be removed from the element, allowing it to load its actual background image.

Lasty, add reference to script.js to index.html. Reload the page and background images should now load automatically once they enter the viewport.


Note that this script queries for all lazy-loadable nodes exactly once immediately after page load. If your page’s DOM tree changes afterwards, you will need to adjust this script to also listen to changes in the DOM tree.

Also note that this loading technique assumes IntersectionObserver is available and does not handle the case where that assumption is not true. This Web API is widely available, but for those browsers where this API is not available, all images will be loaded normally after page load (see script.js lines 27-30).

If you are curious about seeing a real-life demo, check out this page.


Happy Coding!

If you enjoyed this article please share it with your friends!

Mobile First