Skip to main content

Understanding lazy loading in JavaScript

Understanding Lazy Loading In JavaScript

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 isIntersectionintersectionRatiointersectionRecttargettime, 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 intersectionObserverconstructor/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.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...