Lazy loading is a technique that defers loading of non-critical resources at page load time. Instead, these non-critical resources are loaded at the moment of need. Where images are concerned, “non-critical” is often synonymous with “off-screen”.
If you have used Lighthouse and examined some opportunities for improvement, you may have seen some guidance in this realm in the form of the Defer offscreen images audit:

One of Lighthouse’s performance audits is to identify off screen images, which are candidates for lazy loading.
An example of image lazy loading can be found on the popular publishing platform Medium, which loads lightweight placeholder images at page load, and replaces them with lazily-loaded images as they are scrolled into the viewport.

An example of image lazy loading in action. A placeholder image is loaded at page load (left), and when scrolled into the viewport, the final image loads at the time of need.
Why lazy-load images instead of just loading them?
Because it’s possible you are loading stuff the user may never see. This is problematic for a couple reasons:
- It wastes data.
- It wastes processing time, battery, and other system resources.
Lazy loading images and video reduces initial page load time, initial page weight, and system resource usage, all of which have positive impacts on performance.
Implementing Lazy Loading (HTML Way)
The concept of lazy loading is really helpful if you want your websites to load quickly. In HTML, we can use the attribute loading
and set its value to lazy
. This will automatically load images in a lazy fashion.
<html> <head> </head> <body> <div> <img src="photo.jpg" alt="img" loading="lazy" /> </div> </body> </html>
Picture element and lazy loading
Images that are defined using the <picture>
element can also be lazy-loaded:
<picture> Â <source media="(min-width: 800px)" srcset="large.jpg 1x, larger.jpg 2x"> Â <img src="photo.jpg" loading="lazy"> </picture>
Although a browser will decide which image to load from any of the <source>
elements, the loading attribute only needs to be included to the fallback <img>
element.
Avoid lazy loading images that are in the first visible viewport
You should avoid setting loading=lazy
for any images that are in the first visible viewport. This is particularly relevant for LCP images.
Generally, any images within the viewport should be loaded eagerly using the browser’s defaults. You do not need to specify loading=eager
for this to be the case for in-viewport images.
<!-- visible in the viewport --> <img src="product-1.jpg" alt="..." width="200" height="200"> <img src="product-2.jpg" alt="..." width="200" height="200"> <img src="product-3.jpg" alt="..." width="200" height="200"> <!-- offscreen images --> <img src="product-4.jpg" loading="lazy" alt="..." width="200" height="200"> <img src="product-5.jpg" loading="lazy" alt="..." width="200" height="200"> <img src="product-6.jpg" loading="lazy" alt="..." width="200" height="200">
Lazy loading CSS background images
While <img>
tags are the most common way of using images on web pages, images can also be invoked via the CSS background-image
property. Browser-level lazy loading does not apply to CSS background images, so we need to consider other methods if we have background images to lazy-load.
Unlike <img>
elements which load regardless of their visibility, image loading behavior in CSS is done with more speculation.
This speculative behavior can be used to defer the loading of images in CSS by using JavaScript to determine when an element is within the viewport, and subsequently applying a class to that element that applies styling invoking a background image.
This causes the image to be downloaded at the time of need instead of at initial load. For example, let’s take an element that contains a large hero background image.
<div class="lazy-background"> <h1>Here's a hero heading to get your attention!</h1> <p>Here's hero copy to convince you to buy a thing!</p> <a href="/buy-a-thing">Buy a thing!</a> </div>
The div.lazy-background
element would normally contain the hero background image invoked by some CSS.
In this lazy loading example, however, you can isolate the div.lazy-background
element’s background-image
property via a visible class added to the element when it’s in the viewport:
.lazy-background { background-image: url("hero-placeholder.jpg"); /* Placeholder image */ } .lazy-background.visible { background-image: url("hero.jpg"); /* The final image */ }
From here, use JavaScript to check if the element is in the viewport (with Intersection Observer API), and add the visible class to the div.lazy-background
element at that time, which loads the image:
document.addEventListener("DOMContentLoaded", function() { var lazyBackgrounds = [].slice.call(document.querySelectorAll(".lazy-background")); if ("IntersectionObserver" in window) { let lazyBackgroundObserver = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { if (entry.isIntersecting) { entry.target.classList.add("visible"); lazyBackgroundObserver.unobserve(entry.target); } }); }); lazyBackgrounds.forEach(function(lazyBackground) { lazyBackgroundObserver.observe(lazyBackground); }); } });
Example:
See the Pen Lazy Loading CSS Background Images by Sunil Pradhan (@Sunil_Pradhan) on CodePen.
Best Practices
The best practice of setting dimensions applies to <img>
tags regardless of whether or not they are being loaded lazily. With lazy loading, this can become more relevant.
Setting width
and height
on images in modern browsers also allows browsers to infer their intrinsic size.
In most scenarios images still lazy-load if dimensions are not included, but there are a few edge cases you should be aware of. Without width
and height
specified, image dimensions are 0×0
pixels at first.
If you have a gallery of such images, the browser may conclude that all of them fit inside the viewport at the start, as each takes up practically no space and no image is pushed offscreen.
In this case the browser determines that all of them are visible to the user and decides to load everything.
Hence, when using lazy-loading, you should specify the dimensions (width and height) because the browser does not know the dimension on lazy load initially. Without dimension, it is possible to have layout shifts in your site.
Lazy Loading for Video
As with image elements, you can also lazy-load video. How to lazy-load <video> depends on the use case, though. Let’s discuss a couple of scenarios that each require a different solution.
For video that doesn’t autoplay
For videos where playback is initiated by the user, specifying the preload attribute on the <video> element may be desirable:
<video controls preload="none" poster="one-does-not-simply-placeholder.jpg"> <source src="one-does-not-simply.webm" type="video/webm"> <source src="one-does-not-simply.mp4" type="video/mp4"> </video>
📣 Note
A video poster image can qualify as an LCP candidates. If your poster image is an LCP candidate, you should preload it with a fetchpriority
attribute value of “high
” so the user sees it as soon as possible.
The example above uses a preload attribute with a value of none to prevent browsers from preloading any video data. The poster attribute gives the <video>
element a placeholder that will occupy the space while the video loads.
Unfortunately, it doesn’t prove useful when you want to use video in place of animated GIFs, which is covered next.
For video acting as an animated GIF replacement
Using the <video>
element as a replacement for animated GIF is not as straightforward as the <img>
element. Animated GIFs have three characteristics:
- They play automatically when loaded.
- They loop continuously.
- They don’t have an audio track.
Achieving this with the <video>
element looks something like this:
<video autoplay muted loop playsinline> <source src="one-does-not-simply.webm" type="video/webm"> <source src="one-does-not-simply.mp4" type="video/mp4"> </video>
The autoplay, muted, and loop attributes are self-explanatory. playsinline is necessary for autoplaying to occur in iOS. Now you have a serviceable video-as-GIF replacement that works across platforms. But how to go about lazy loading it? To start, modify your <video>
markup accordingly:
<video class="lazy" autoplay muted loop playsinline width="610" height="254" poster="one-does-not-simply.jpg"> <source data-src="one-does-not-simply.webm" type="video/webm"> <source data-src="one-does-not-simply.mp4" type="video/mp4"> </video>
You will notice the addition of the poster attribute, which lets you specify a placeholder to occupy the <video> element’s space until the video is lazy-loaded.
You’ll notice the addition of the poster attribute, which lets you specify a placeholder to occupy the <video>
element’s space until the video is lazy-loaded. As with the <img>
lazy-loading the video URL in the data-src
attribute on each <source>
element.
From there, use JavaScript code similar to the Intersection Observer-based image lazy loading examples:
document.addEventListener("DOMContentLoaded", function() { var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy")); if ("IntersectionObserver" in window) { var lazyVideoObserver = new IntersectionObserver(function(entries, observer) { entries.forEach(function(video) { if (video.isIntersecting) { for (var source in video.target.children) { var videoSource = video.target.children[source]; if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") { videoSource.src = videoSource.dataset.src; } } video.target.load(); video.target.classList.remove("lazy"); lazyVideoObserver.unobserve(video.target); } }); }); lazyVideos.forEach(function(lazyVideo) { lazyVideoObserver.observe(lazyVideo); }); } });
When you lazy-load a <video>
element, you need to iterate through all of the child <source>
elements and flip their data-src
attributes to src
attributes.
Once you have done that, you need to trigger loading of the video by calling the element’s load method, after which the media will begin playing automatically per the autoplay
attribute.
Using this method, you have a video solution that emulates animated GIF behavior, but doesn’t incur the same intensive data usage as animated GIFs do, and you can lazy-load that content.
Lazy loading for iframes
Standardized lazy-loading of iframes defers offscreen iframes from being loaded until the user scrolls near them. This saves data, speeds up the loading of other parts of the page, and reduces memory usage.
<iframe src=https://example.com loading="lazy" width="600" height="400"></iframe>
If you need to dynamically create iframes via JavaScript, setting iframe.loading = 'lazy'
on the element is recommended:
var iframe = document.createElement('iframe'); iframe.src = 'https://example.com'; iframe.loading = 'lazy'; document.body.appendChild(iframe);
Lazy-loading YouTube video embeds (Example)
<iframe src="https://www.youtube.com/embed/YJGCZCaIZkQ" loading="lazy" width="560" height="315" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen> </iframe>
Implementing lazy loading (JavaScript Way)
There are a lot of snippets and scripts out there that enable lazy-loading via JavaScript. In almost all cases a data attribute is used to prevent upfront image loading.
But how is the image loaded at all? To achieve this, one of the following two techniques is usually used.
Event listeners
This technique uses event listeners on the scroll, resize and orientationChange events in the browser.
If one of the mentioned events is fired and assuming the image enters the viewport the data-src attribute is replaced with the src attribute to trigger the loading call.
Example:
See the Pen Lazy loading images using event handlers – example code by ImageKit.io (@imagekit_io) on CodePen.
Intersection Observer API
Unlike the first method, the image is observed (asynchronously) by using the IntersectionObserver API. The image is then loaded by changing the data-src to src attribute as soon as it enters the viewport.
See the Pen Lazy loading images using IntersectionObserver – example code by ImageKit.io (@imagekit_io) on CodePen.
FAQs
Lazy Loading Library
Further Reading
- Browser-level image lazy loading for the web
- It’s time to lazy-load offscreen iframes!
- The Complete Guide to Lazy Loading Images
- Native lazy-loading (loading=lazy) not working even with flags enabled
Great information shared.. really enjoyed reading this post thank you author for sharing this post .. appreciated
Nice approach. I’ll have to try this in my next project.
Lazy loading is a crucial technique that can significantly enhance user experience by reducing page load times.
The article’s step-by-step breakdown of the lazy loading process for both images and videos is commendable. The explanation of how lazy loading works, along with its benefits, provides a strong foundation for readers, regardless of their technical expertise.
One suggestion for further enhancement could be to elaborate on scenarios where lazy loading might not be suitable, such as certain single-page applications or interactive content-heavy websites. This would help readers understand the context in which to apply this technique effectively.
The article offers a comprehensive yet accessible explanation of the lazy loading technique, emphasizing its role in improving website performance. The step-by-step guide, accompanied by code examples, simplifies the implementation process and ensures developers of all levels can effectively leverage lazy loading.
The article effectively addresses the importance of website performance in the present digital age, where users’ expectations for speed and responsiveness are higher than ever. By focusing on lazy loading, the article demonstrates a keen understanding of a strategy that can significantly contribute to achieving these expectations.
In conclusion, this updated guide not only underscores the author’s commitment to staying current with web development trends but also highlights their dedication to empowering fellow developers with actionable strategies. As we continue to navigate the intricate realm of website performance, this guide serves as a beacon for crafting faster, more efficient, and ultimately more satisfying digital experiences.
The comprehensive insights provided into the art of lazy loading go beyond just the basics, delving into the evolving landscape of web performance optimization. The inclusion of updated techniques and best practices demonstrates the author’s commitment to staying current in an ever-changing field.
The article adeptly addresses the ongoing significance of optimizing website speed and the role that lazy loading plays in achieving this goal. By incorporating the latest practices and techniques, the article acknowledges the ever-evolving nature of web development and the constant need to adapt to changing user expectations.
The insights shared in this updated article are incredibly relevant and timely. In a digital landscape where user experience and website performance are paramount, the focus on lazy loading is both strategic and practical.
The article demonstrates a clear understanding of the challenges that slow-loading images and videos pose to modern websites. By delving into the mechanics of lazy loading, the article effectively showcases its ability to defer the loading of non-essential content, thus improving initial page load times and conserving bandwidth.
The step-by-step instructions provided for implementing lazy loading are concise and easy to follow, making it accessible even for those less familiar with the technical aspects of web development. The inclusion of code snippets and examples further enhances the article’s practicality, enabling developers to seamlessly integrate lazy loading into their projects.
The step-by-step instructions on implementing lazy loading for both images and videos are the standout features of the article. The detailed explanations, accompanied by code snippets, make it easy for developers to grasp the technicalities and apply them to their own projects.