Images and other media files are usually large in size and contribute the largest part to a website's payload. When you visit a website that doesn't implement lazy-loading, the browser loads every single resource on the opened page, regardless of whether the user ever scrolls down to view such off-screen content.
The richer the content on the page, the more stuff will likely be loaded unnecessarily and the longer the user has to wait until the page loads. This is a big waste of resources in terms of data transfer, processing time, and memory usage. But it also affects the overall user experience and performance of your website, and therefore also hurts from a SEO perspective. Taken together, all these factors are ultimately bad for your business and for the environment.
Lazy-loading of resources, on the contrary, delays the loading of below-the-fold resources until they are actually needed, i.e., until the user scrolls near them, which results in a much smaller initial payload and improved performance.
And the good news is that lazy-loading is pretty easy to implement. In the next sections, we'll look at our options.
For images, lazy-loading should always be accompanied by image optimization, the use of modern image formats, and a responsive image strategy to generally reduce the amount of data that needs to be loaded when requesting these resources.
When you visit a page, and as you begin to scroll down, at some point as you approach an image below the fold, usually a placeholder (e.g. a low quality image or solid color block) appears in the viewport, which is then replaced by the final image. Whether you will actually ever see the placeholder depends on multiple factors like the number of images that have to be loaded, your network speed or when the replacement actually takes place.
With native support for lazy-loading images in modern browsers, we now have more options that can also be combined. So let's look at how to implement different options and their pros and cons.
There are several ways to implement lazy-loading on your website. So what are the decisive factors for preferring one solution over the other? They boil down to these:
- The browsers you need to support
- The type of resource you want to lazy-load
- The amount of control you need over the lazy-loading behavior
The easiest way to implement lazy-loading is by leveraging what is often referred to as "Native Lazy Loading", a browser feature which is supported in most up-to-date browsers at the time of writing this recipe, albeit with varying support (some browsers like Firefox currently only support images). Check out Caniuse.com for details.
To implement browser-level lazy-loading, use the
loading attribute with the value set to
lazy on the
img element. This attribute tells the browser when it should start loading the resource.
Responsive images with
When using the
picture element, the
loading attribute only needs to be set on the fallback
This works, because the image is displayed by the
img element, while the
source elements only provide the options the browser will choose from.
Browsers that don't support the
loading attribute supports three values that determine when a resource should be loaded:
lazy: Used to defer fetching a resource until some conditions are met.
eager: Used to fetch a resource immediately; the default state.
auto(only Chrome, not part of the specification): Let the browser determine when to load the resource, same as not using the attribute at all.
When exactly a resource is loaded depends on the distance-from-viewport thresholds, which are not fixed and depend on browser engine and connection speed and may be subject to change in the future (see for example Google lazy-loading article.
But there are also some downsides:
- Browser-level lazy-loading is limited to images and iframes (depending on browser support) and not supported by older browsers.
- Browser implementation determines the distance threshold, that is when exactly the resource is loaded, therefore you have no control over this behavior.
- Lazy-loading for background images is not supported.
Because of these thresholds, images are loaded rather early while they are still quite a bit out of the visible area, so they will usually be loaded when a user reaches a lazy-loaded image. Especially on a fast internet connection, you will therefore hardly be able to notice when exactly the images are loaded.
But you can check what is loaded and when if you open the network tab in your browser's developer tools. Create a page with multiple (full-width) images below each other, then open the page in the browser with the network tab open. Set the filter to only view images to make it easier. Then observe how images below-the-fold are loaded one by one as you scroll down.
It's also interesting to see how different browser engines behave and also how the behavior changes (for example in Chrome) when you enable network throttling.
For your convenience, I created a testsite on GitHub with some examples for the different techniques introduced in this recipe.
- Download the script and put it into
- Load it using the
Adapt the path to the file if you put it in another location.
You can include this script at the end of the body element before any blocking elements or in the head after any blocking elements, see as described in the documentation.
And that's it. The script doesn't need to be initialized but works by adding a class and data attributes to your markup, as we will see in the following examples.
For the most basic markup, you replace the
src attribute of an image element with a
src attribute is a way to prevent that.
The downside of this approach is that you actually load two images, the low quality one and the final one.
An alternative could be to add a
<noscript> element with a link to the image instead of using a
If you modify the test page (see above) to use Lazysizes instead of browser-level lazy-loading and open the network tab in your browser's dev tools again, you can observe if and how the behavior is different from native lazy-loading.
Lazysizes also works with responsive images by replacing
data-srcset. To make it really easy, we set
auto to leave it to Lazysizes to do the calculations for us.
data-sizes="auto", Lazysizes will make sure that the best-fitting image is used depending on how much screen-space the image is actually using.
Play around with the settings and observe the results in your developer tools.
picture element, add the
lazyload class to the
img element and use data-srcset on your
source and the
See the examples and the documentation of the library for more details and configuration options. There are also a few plugins available to lazyload other resources like background images, videos etc.
Lozad.js is less than half the size of Lazysizes (1.25 kB minified & gzipped), and uses the IntersectionObserver API for lazy-loading. Since browser support for the IntersectionObserver API is more limited, you need a polyfill for browsers that don't support it.
Download the script and include it just before the closing body tag (adapt path as needed):
Then initialize the script:
If you want to reuse our previous Lazysizes examples without changing the class attribute, you can use a different selector when initializing the script:
The markup is similar to Lazysizes: you add a (configurable) class attribute with value
lozad and a
data-src attribute with the image url.
Lozad also supports responsive images with the
data-srcset attribute just like Lazysizes:
However, since Lozad doesn't support auto-sizes like Lazysizes does, you will have to set sizes on the srcset options yourself if the images aren't used full-screen. We will cover responsive images in detail in a future recipe.
To make Lozad work with the
picture element, you have to set a minimum height and a display type other than
inline, otherwise the IntersectionObserver will not detect it:
Note that the
img element is not added.
The min height on the
picture tag should be set to a value that equals the actual height of the image. Setting it to
1rem as in the example will likely result in all images being loaded at once. It might therefore make sense to set this value via JS.
A more modern approach would involve setting the aspect-ratio CSS property on the picture tag and then absolute position the img element inside it.
You can find all options and more examples in the documentation.
The third library I'd like to introduce here is Vanilla Lazyload. Compared to the other two mentioned above it's probably a lesser known. Like Lozad, it relies on the IntersectionObserver API, with a size of 2.4 kB minified and gzipped it takes a middle position. The library has extensive documentation and a lot of examples for different use cases. It supports images, animated SVGs, videos and iframes, responsive images and can enable native lazy-loading.
- Download and include the script before the closing
- Initialize the script
The markup is the same as in the previous example in that you use the
And for the
picture element, you cadd a
data-src attribute on the
img element and a
data-srcset attribute on the
As placeholder, you can again fall back to a a low quality image in the
src attribute of the
img element or use another placeholder strategy.
You can use any of the above libraries as fallback to native lazy-loading in unsupporting browsers. This works with the following pattern:
data-srcattribute and add
loadingattribute on below-the-fold images:
- Use a little script to replace the
data-srcattribute with a
srcattribute for browsers that support the
loadingattribute, otherwise load the fallback library and initiate it.
Use with caution if you use placeholder images in a
src attribute. The
loading attribute interferes, since all
data-src attributes will be converted to
src once this script loads and would therefore overwrite all placeholder images. If the final images are loaded slower than the user scrolls, the user will see blank space instead.
Lazysizes also has a native loading extension that automatically uses native lazy-loading if available and otherwise falls back to the library's functionality.
Note that the same approach can also be implemented with the other libraries.
- It is generally recommended to avoid using lazy-loading on critical, above-the-fold content that should load as fast as possible. Since what is above-the-fold changes with the devices your website visitors are viewing your website/app with, it is a good idea to be rather generous when trying to determine this imaginary line. On a mobile device, maybe only the first image is in the viewport on first load, on a large desktop monitor, however, there might be quite a few critical images.
- Use a placeholder strategy for your images and videos to prevent layout shifts as content is loaded on demand.
- Images should include dimension attributes. Without dimensions specified, layout shifts can occur, which are more noticeable on pages that take some time to load. Layout shifts are not only bad from a user's experience point of view, but also for performance, because the browser has to recalculate the layout. Reserving space for the images can, for example, be achieved by setting a width and height attribute on the
imgelement, or by setting the
aspect-ratioCSS property on a containing element and then absolute positioning the image inside.
I created a little testsite with the examples in this recipe. While clicking through the different pages you can compare how the different approaches vary in how many images they load inititially and thus how the initial payload and loading speed differs.
The examples are all implemented with the default settings as in the examples in this recipe. For more options/config settings see the documentation of each library.
As we have seen, lazy-loading only requires very small changes to our usual markup and is therefore quickly implemented. The gain in performance, however, is huge, so there is really no reason at all not to do it.
If you can live with supporting modern browsers and your site is not very image heavy, it will probably suffice to implement native lazy-loading as progressive enhancement, ideally in conjunction with optimized images and modern image formats.
As a fallback for browsers that don't support native lazy-loading, or if you need more control over the behavior or want to support other resources like videos as well, use a lazy-loading library.