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 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
250milliseconds. This means that the browser will wait for250milliseconds 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
imgelements with the"lazy"class (see the third and fourth images with the"lazy"class in theindex.htmlfile) in theloadImagesvariable.Line 4: We declare a variable to manage timeout.
Lines 8–9: We check if
loadTimeoutis already set (i.e., if there’s a timeout in progress). If so, we clear that timeout usingclearTimeoutto 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., theimgis in or near the viewport), the line sets thesrcattribute of theimgelement to the value of itsdata-srcattribute. This is a common technique for lazy loading images. Thedata-srcattribute 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.lengthis 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
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
IntersectionObserverto 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
srcattribute to the value of thedata-srcattribute. This loads the actual resource (e.g., image).Line 8: The
unobserve()method is part of theIntersectionObserverinterface, 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
IntersectionObserverwith a threshold of0.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