Understanding lazy loading in JavaScript

In this article, we will explore the concept of lazy loading in JavaScript. We'll delve into the native lazy loading API, its implementation, the significance and benefits of lazy loading, and a practical example of lazy loading web content. To make the most of this tutorial, readers should have a fundamental understanding of web application development using JavaScript.

Understanding the lazy loading API and its inner workings will provide valuable insights for developers already working with libraries and frameworks that employ these techniques. It will empower them to conduct more informed research and apply these methods should they decide to create their own lazy loading library.

In a real-world scenario, marketing and advertising companies that generate revenue through platform advertisements can leverage lazy loading to optimize their operations. This allows them to track which ads are viewed by users on their platform, enabling data-driven business decisions. Let's begin.

What is lazy loading?

According to Wikipedia, lazy loading is a technique crafted to postpone the activation of an element or an object until it becomes necessary. In this method, a specific DOM element, in relation to a parent DOM element, is loaded and made visible (once they intersect, based on a predefined threshold) only when a user scrolls through them on a webpage.

The disadvantages of not employing this pattern can lead to a 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.

Neglecting to lazy load can also cause an increase in page load time due to the size of the bundle to be downloaded/fetched. Low user retention is also mostly applicable in areas with poor internet connectivity. It is not uncommon for users to avoid a platform entirely when developers make the mistake of not implementing lazy loading early on.

And lastly, a large impact on web performance and accessibility is 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 load many images and videos simultaneously on initializing the browser DOM.

Based on the data, it's evident that the majority of websites depend significantly on images, as well as other web content such as videos and iframes, to convey information to their intended audience. Although this may appear straightforward, the way we present this content to our users plays a crucial role in determining the overall performance of our platform.

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 learn more about other use cases in real-life environments.

Advantages of lazy loading

By now, we should better understand 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: Web accessibility is extremely important. Using this technique helps to build 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 will thank you later!
  • Infinite scroll: 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

Native lazy loading API

Lazy loading is based on the Intersection Observer API, which is a web API used to detect when a specific element, known as the target, becomes visible within the browser's viewport or intersects with another element, typically its parent element. This API allows us to trigger a handler function when such interactions occur, enabling us to manage various aspects of our code logic. To fully comprehend lazy loading, it's essential to first grasp the fundamentals of the Intersection Observer API and how to utilize it.

Creating an intersection observer

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 browser event almost similar to the document event category, which includes the DOMContentLoaded event.

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

Additionally, we also need to specify a margin for the root (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 (callback, options);

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 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 null, the parent element automatically becomes the viewport.

Lastly, rootMargin helps to set the margin around the root. 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 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 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. Afterward, we observed the target 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 to observe. Note that the intersection observer likewise has a bunch of methods in its API: unObserve(), takeRecords(), observe(), etc., are some examples.

Implementing lazy loading

In this section, we’ll go over some techniques we can use to incorporate lazy loading in our applications to make rendering images more user-friendly and smooth. First, we can use the default browser attribute. This involves using the loading attribute, which can be used on <img> and <iframe> HTML elements. Setting the loading to "lazy" lets the browser know to load the element as it approaches the viewport.

Secondly, we can use the intersection observer. We’ll begin by customizing the options object for the target element we intend to observe for intersection:

let  options = {
root: null, // Use the viewport as the root
rootMargin: '0px' // Specify the threshold for intersection
};

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

const lazyImages = document.querySelectorAll(".lazy");

Now, let’s look at implementing the callback:

const handleIntersection = (entries, observer) => {
  entries.forEach((entry) => {
    console.log(entry);
    if (entry.isIntersecting) {
      const img = entry.target;
      const src = img.getAttribute("data-src");
      // Replace the placeholder with the actual image source
      img.src = src;
      // Stop observing the image
      observer.unobserve(img);
    }
  });
};

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, as shown below:

const observer = new IntersectionObserver(handleIntersection, options);

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

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

Here’s the finished result:

The HTML and CSS code are not included here for simplicity. But, you can find them in the CodeSandbox snippet above. Feel free to fork and play around with the code as much as you like. You can also learn more about the intersection observer API here.

What other page resources can use lazy loading?

It’s important to note that lazy loading does not only apply to images. In fact, many other page resources can benefit from using lazy loading. Let’s take a look at them:

JavaScript

JavaScript can hinder the rendering of a page by the browser until the content of the script is done loading. This is known as render-blocking. Luckily, JavaScript code can be split up into smaller pieces known as modules. Writing modular JavaScript code can help reduce load time significantly for a page that requires JavaScript to be executed.

CSS

CSS code also classifies as a render-blocking resource. Splitting a CSS file into multiple files that only load when necessary can help reduce the time a browser is blocked from rendering the rest of the page. Learn more about optimizing your CSS to prevent render-blocking here.

Inline Frame

An Inline Frame, commonly referred to as iframes, is an HTML element used to embed another element within a webpage. By configuring the loading attribute as "lazy" for an iframe, you can facilitate the lazy loading of its content.

Lazy loading best practices

Let’s review some lazy loading best practices and discuss when and when not to lazy load resources in our applications. Here are some best practices for using lazy loading with the Intersection Observer API:

  • Identify the elements to lazy load: Determine which resources on your webpage are non-critical and can be deferred until they come into view or are about to be interacted with by the user, such as off-screen images or below-the-fold content
  • Set up the Intersection Observer: Create an instance of the Intersection Observer API and configure it to observe the target elements. This involves specifying a callback function that will be triggered when the observed elements intersect with a specified root element or the viewport
  • Implement the lazy loading logic: In the Intersection Observer callback function, handle the logic for loading the resources when they become visible. This typically involves replacing the placeholder or data-src attributes of the elements with the actual source URL of the resource, triggering the network request, and loading the content
  • Use appropriate loading attributes: For images, consider using the loading attribute to optimize the lazy loading process further. Set the value to "lazy" to let the browser handle the lazy loading behavior automatically, or set it to "eager" for critical images that should be loaded immediately
  • Provide fallback content: While lazy loading improves performance, ensuring a good UX is essential. Provide fallback content, such as placeholders or low-resolution versions of the resources, so users can still understand the context while waiting for the full content to load. Additionally, users that disable JavaScript on their browsers offer a noscript variant as they won’t be able to see lazy loaded resources

When to lazy load resources

  • Large images or media files: Lazy loading is particularly useful for images or media files that are not immediately visible to the user, reducing the initial page load time and bandwidth usage
  • Infinite scroll or dynamically loaded content: When implementing infinite scroll or loading content dynamically as the user scrolls, lazy loading ensures that only the necessary content is loaded as it becomes visible
  • Performance optimization: Lazy loading can significantly improve the performance of webpages, especially those with a large amount of content or resources

When to avoid lazy loading

  • Critical resources: Avoid lazy loading resources crucial for the initial page rendering or the UX. This includes content above the fold, important scripts, or interactive elements necessary for the core functionality of the page
  • Small or fast-loading resources: If the resources are small in size or load quickly, the benefits of lazy loading may be negligible, and it may be more efficient to load them immediately
  • SEO considerations: Search engine crawlers may not execute JavaScript or wait for lazy loaded content to load. If the content you want to lazy load is essential for SEO or indexing purposes, consider alternative methods, such as preloading or server-side rendering

Summary

Now, the benefits of implementing the lazy loading technique become quite evident when dealing with a large number of images or videos on a web page and loading them all at once during the initialization of the browser's Document Object Model (DOM).

As developers, it is our responsibility to guarantee the best performance for the platforms we oversee, particularly when they have a direct impact on business goals. Lazy loading, as a web performance strategy, effectively addresses these issues.

No comments:

Post a Comment