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 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.
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.
There are different ways to implement lazy loading of images using JavaScript. We’ll go through two such ways:
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
250
milliseconds. This means that the browser will wait for250
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.
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.
There’s another way to load the images using the
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.
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