How to do lazy loading of images in JavaScript

Sometimes we see images being loaded as we scroll down a web page. This delayed loading of images is done on purpose using the lazy loading technique.

Lazy loading

Lazy loading is a technique where images (or other resources like audio files, video files, etc.) are not immediately loaded when someone visits a website. Instead, images are loaded after the initial content, which is visible without scrolling, is fully loaded, or only when images enter the browser’s viewable area as we scroll down. This means that if a visitor doesn’t scroll down the web page, the images at the bottom of the web page remain unloaded.

Benefits of lazy loading

Oftentimes, visitors on a web page interact with only the top part of the page and don’t even scroll down. Lazy loading helps defer loading the resources that aren’t needed right away. This, in turn, helps reduce bandwidth and page load time and improve the user’s experience.

Lazy loading in JavaScript

There are different ways to implement lazy loading of images using JavaScript. We’ll go through two such ways:

1. Using the event listener

When we scroll down, the images in or near the viewportA viewport is the visible part of the web page. are triggered to load using the scroll event listener.

Look at the JavaScript code in the example below that waits until the images are in the viewport (visible on the screen) before loading them.

Note: To clearly show the lazy loading, we’ve increased the timeout value to 250 milliseconds. This means that the browser will wait for 250 milliseconds after the user interacts with the page (e.g., scrolling) before checking for images in the viewport and loading them. We can adjust the time as needed.

Tip: It’s better to not lazy load an image that’s just slightly off the viewport (the second image, in our case) for a better user experience. In our example above, we have lazily loaded only the third and fourth images.

Code explanation

The index.html section includes some dummy text and images for demonstration. In the code in the index.js file, we’ve set up an event listener for "scroll" events. When the scroll event occurs, the lazyload function is executed to check if elements with the "lazy" class are within the viewport and, if so, to load them lazily.

  • Line 3: We select and store all img elements with the "lazy" class (see the third and fourth images with the "lazy" class in the index.html file) in the loadImages variable.

  • Line 4: We declare a variable to manage timeout.

  • Lines 8–9: We check if loadTimeout is already set (i.e., if there’s a timeout in progress). If so, we clear that timeout using clearTimeout to optimize performance.

  • Line 12: We retrieve the current vertical scroll position of the page to see how far has the page been scrolled.

  • Lines 14–15: The code checks if the top offset (position from the top of the document) of the image element (img.offsetTop) is less than the sum of the window’s inner height and the current vertical scroll position (window.innerHeight + scrollTop). If this condition is met (i.e., the img is in or near the viewport), the line sets the src attribute of the img element to the value of its data-src attribute. This is a common technique for lazy loading images. The data-src attribute typically holds the actual image source, and it’s swapped in when the image becomes visible.

  • Lines 22–25: If all the lazy-loaded images have been loaded (i.e., lazyloadImages.length is 0), we remove the event listener for scrolling. This is an optimization to stop monitoring for the event once all the images have been loaded.

Using the Intersection Observer API

There’s another way to load the images using the Intersection Observer API It enables developers to track changes in the intersection of a target element with its parent element or the viewport. This API facilitates the efficient detection of when an element enters or exits the user’s viewport or intersects with ancestor elements.. This is a powerful and versatile way to lazily load resources when they come into the viewport or meet specific conditions.

Code explanation

Here, in the index.js file, we’ve created an IntersectionObserver object that monitors the visibility of images with the lazy class. The intersection observer callback function gets executed whenever an observed element intersects with the viewport.

When these images are visible to a specified extent (in this case, 60% of the element is visible), they’re loaded, improving page load performance.

  • Line 1: We create an IntersectionObserver to watch for elements that come into view.

  • Line 4: Iterating through each entry (element being observed), we check if the element is intersecting with the viewport.

  • Lines 5–7: If the element is intersecting, set its src attribute to the value of the data-src attribute. This loads the actual resource (e.g., image).

  • Line 8: The unobserve() method is part of the IntersectionObserver interface, and it’s used to stop observing a particular target element. Here, we want to stop observing the element to optimize performance once it becomes visible. This can be helpful in scenarios where we only want to observe an element until a specific condition is met (e.g., image loaded) and then stop observing it to avoid unnecessary callbacks and computations for that element.

  • Line 12: We configure the IntersectionObserver with a threshold of 0.6, meaning 60% visibility is required for the callback to be triggered.

  • Lines 14–19: We select all elements with the class lazy (lazy-loaded images) on the page, iterating through each lazy-loaded image and instructing the observer to observe it. When an image becomes visible, the callback function is triggered to load the image.

Conclusion

For simpler projects or cases where a lightweight solution is sufficient, event listeners can be a straightforward choice. However, for larger and more complex projects, or when optimizing performance is a priority, the Intersection Observer API is recommended due to its efficiency and built-in features. We should consider the specific needs of our project and the level of control required when deciding between these two methods.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved