Introduction

In this post, we are going to look at how lazy loading works on the web. We will cover the native lazy loading API — how lazy loading is implemented, the importance and advantages of lazy loading, and, finally, a simple use case of lazy loading web content. To effectively follow along this tutorial, it is assumed that readers have a basic understanding of building web applications with JavaScript.

Understanding the lazy loading API and how it works will help developers who already work with libraries and frameworks that implement these techniques to understand what goes on under the hood. Additionally, they’ll be able to perform more guided research and apply the techniques they learn if they ever intend to implement their own lazy loading library.

As for a real-world use case, marketing and advertising firms who make their revenue off advertisements on their platform can easily optimize and apply lazy loading so as to easily tell which ads are seen by users who visit their platform and thereby make better business decisions.

What is lazy loading?

According to Wikipedia, lazy loading is a pattern designed to hold off the initialization of an element or an object until it is needed. What this means is that a target DOM element, relative to a parent DOM element, is loaded and becomes visible (when there is an intersection between both elements, based on a set threshold value) only when a user scrolls through them on a webpage.

The disadvantages of not employing this pattern can lead to:

Huge lag in page performance due to multiple synchronous network requests or batch requests to fetch a couple of images or other web resources from one or more sources

Increase in page load time due to the size of the bundle to be downloaded/fetched

Low user retention, mostly applicable in areas with poor internet connectivity. It is not uncommon for users to avoid a platform entirely when we developers make the mistake of not implementing lazy loading early on

A huge impact on web performance and accessibility caused by resources or assets like images, iframes, and videos that are not properly handled

Currently, lazy loading is natively supported on the web for most modern and updated browsers. However, for browsers that don’t offer this support yet, polyfills or libraries that implement this technique provide simple API layers above them.

Lazy loading solves the problem of reducing initial page load time — displaying only resources like images or other content that a user needs to see on initializing a webpage and as the page is subsequently scrolled.

Web performance and accessibility issues are known to be multifaceted; reducing page size, memory footprint, and general load time can contribute a great deal to a web platform. The advantages of lazy loading become obvious when we have a bunch of images and videos, and we load them all at once on initialization of the browser DOM. Certainly, you should now have an understanding of what this will lead to, as we have earlier discussed.

Judging by the data, most websites rely heavily on images and other web content like videos or iframes to pass information across to their target audience. While this might seem trivial and simple, the way we display this content to our users determines how performant our platform is at the end of the day.

Furthermore, actions that would help optimize our page load time, like events that are dependent on whether a user scrolls to a particular portion of our webpage, are some of the use cases of lazy loading. As we continue with this article, we will get to learn more about other use cases in real-life environments.

Native lazy loading API

Lazy loading is built on top of the Intersection Observer API, which is a browser API that provides a way of detecting or knowing when an element called a target, a parent element, or becomes available or visible inside the browsers viewport, as the case may be.

When this occurs, a handler function is invoked to help handle other parts of our code logic, as we will see later on. With this new and improved browser API, we can also know when two DOM elements intersect — by this, we mean when a target DOM element enters the browser’s viewport or intersects with another element, which, most likely, is its parent element.

To better understand how lazy loading works, we first of all have to understand how to create an intersection observer. In order to create an intersection observer, all we need to do is listen to the occurrence of an intersection observer event and trigger a callback function or handler to run when this kind of event occurs. The intersection observer event is a kind of browser event that is almost similar to the document event category, which includes the DOMContentLoaded event.

Note: For intersection events, we need to specify the element that we intend to apply the intersection against. This element is usually called the root element. However, if the root element is not specified, it means we intend to target the entire browser viewport.

Additionally, we also need to specify a margin for the root element (if provided) so that we can easily alter its shape or size, if necessary, on intersection. Let’s take a look at an example to understand it better:

let options = { root: null, rootMargin: 10px, threshold: 1.0 } let observer = new IntersectionObserver (options, callback);

In the above snippet, we have seen a simple use case for creating an observer. The options object helps us define custom properties for our target.

Here, the threshold property in the options object signifies when the callback is to be triggered. It has a default value of zero, which means that as soon as a user approaches the target element and it becomes visible, the callback is triggered.

On the other hand, the root is the parent element that acts as the viewport for the target element when the target element becomes visible to the user as they scroll through the webpage. Note that if the root is set to null, the parent element becomes the viewport automatically.

Lastly, rootMargin helps to set the margin around the root element. For example, before we compute the intersection between the target and the parent element/viewport, we might have to tweak its size, margin, or dimension.

Furthermore, the callback, which accepts two input parameters, includes a list of intersectionObserverEntry objects we intend to apply on the target element and the observer for which the callback is being invoked.

The signature of the callback is shown below:

let callback = (entries, observer) => { entries.forEach(entry => { If (entry.isIntersection) { // do some magic here } // and some other methods }) }

The intersectionObserverEntry object signifies when there is an intersection between parent and target elements. It has a bunch of properties in its API, which include isIntersection , intersectionRatio , intersectionRect , target , time , etc. For a detailed explanation of these properties, you can consult this section of the MDN documentation.

We need to target a specific DOM element and trigger a callback function when it intersects with a parent element. An example of a DOM element to target is shown in the code snippet below:

let target = document.querySelector("#targetElement");

In the snippet above, we created a target element and assigned a variable to it. Afterwards, we observed the target element using the observe method on the intersectionObserver constructor/function signature, as shown below:

// start observing for changes on the target element observer.observe(target);

When the threshold set by the observer for the target is reached, the callback is fired. Simple, right?

Lastly, the observe() method tells the observer what target element to observe. Note that the intersection observer likewise has a bunch of methods in its API: unObserve() , takeRecords() , observe() , etc. are some examples.

Advantages of lazy loading technique

By now, we must have a better understanding of why lazy loading web content and assets is necessary. Let’s look at some further advantages of using this technique:

Building web applications that are highly accessible. Talks about web accessibility are on the front burner today. Using this technique would definitely aid in building a platform that has a wider reach

High user retention. If a web platform is tied to driving business objectives and, in turn, providing value, implementing this technique would help a lot in making the platform user-friendly. The web standards would thank you later!

As a developer, you might be tasked with implementing infinite scroll on a web platform. Having an understanding of this concept would help a great deal, thereby providing immediate business value

Implementing lazy loading

Let’s look at a simple example of lazy loading images on a webpage. We’ll begin by customizing the options object for the target element we intend to observe for intersection against:

let options = { root: document.querySelector('.root'), rootMargin: '0px, 0px, 100px, 0px' };

Now, for the target element, let’s target a couple images:

let images = [...document.querySelectorAll('.targetImages')];

Now, let’s look at implementing the callback:

const callback = (entries) => { entries.forEach(entry => { If (entry.isIntersecting) { observer.unObserve('entry.target'); } // handle other code logic here }) }

We can go ahead and call the intersection observer constructor function to observe the target element based on the customizations specified in its options object:

let observer = new intersectionObserver(options, callback);

Finally, we can watch the target element to be observed:

images.forEach(image => { observer.observe(image); })

Note: The HTML and CSS code are not included here for simplicity. You can get a detailed feel of how to implement this technique by checking this example in the MDN docs.

Summary

Now the advantages of this technique should be abundantly clear when we have a bunch of images or videos on a webpage and we load them all together on initialization of the browser DOM. As developers, it is our duty to ensure optimal performance of the platforms we manage or maintain, especially if they are tied to business objectives. Lazy loading as a web performance technique helps to solve these kinds of problems.

LogRocket: Debug JavaScript errors easier by understanding the context Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them. LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error. LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier! Try it for free.