Making the web faster and more user-friendly
Lazy load responsive images in 2020
Do you want to boost performance on your website? You can do that by using responsive images and lazy loading! In this article, you will find the HTML, JavaScript, and CSS code to lazy load responsive images, to make browsers use modern image formats like WebP and Jpeg2000, and to enable native lazy load where supported.
Definitions #
Responsive images are img
tags that download the right image source depending on your design and the user's device. You can provide information about your design in the sizes
attribute and a list of image sources in the srcset
attribute. You can also use media queries by wrapping your img
in a picture
tag. More about responsive images in the MDN.
Lazy loading images is a technique that makes your website render faster by deferring the loading of below-the-fold images to when they enter the viewport. Beyond performance, this also allows you to save bandwidth and money, e.g. if you're paying a CDN service for your images.
Above-the-fold first #
Bear in mind that using a script to lazy load images is a Javascript-based task and it's relevantly slower than the regular image loading (eager loading from now on) which starts as soon as the HTML document is being parsed.
☝️ For this reason, the best practice is to eagerly load above-the-fold images, and lazy load only the below-the-fold images.
At this point, people usually ask:
💬 My website is responsive, how do I know how many images will be above-the-fold at page landing?
The answer is: count them! Open the web page in a browser, resize the viewport to the most common dimensions (smartphones, computers, and tablets) maybe using the device emulation tool, and count them.
If you can see 4 images above-the-fold in a smartphone viewport, plus only the tip of 4 more images on a desktop viewport, be conservative and eagerly load only 4.
Now to some code! #
Here's the HTML markup of an eagerly loaded responsive image.
<!-- Eagerly loaded,
above-the-fold only -->
<img
alt="Eager above"
src="220x280.jpg"
srcset="
220x280.jpg 220w,
440x560.jpg 440w
"
sizes="220px"
/>
And here's the markup to lazy load a responsive image.
<!-- Lazy loaded,
below-the-fold only -->
<img
alt="Lazy below"
class="lazy"
data-src="220x280.jpg"
data-srcset="220x280.jpg 220w,
440x560.jpg 440w"
data-sizes="220px"
/>
Want to show a low-resolution preview while your lazy images are loading? You can do that by using a small, low-quality image in the src
tag, like the following.
<!-- Lazy loaded,
+ low-res preview,
below-the-fold only -->
<img
alt="Lazy below with preview"
class="lazy"
src="11x14.jpg"
data-src="220x280.jpg"
data-srcset="220x280.jpg 220w,
440x560.jpg 440w"
data-sizes="220px"
/>
Open the 👀 demo, then your browser's developer tools, then switch to the Network panel. You will see that the first 2 images are eagerly loaded just after page landing, while the rest of the images are lazily loaded as you scroll down the page.
We're using the img
HTML tag and not the picture
tag, given that the latter is not necessary in this case. I'll dig into the picture
tag use cases later in this article. ⏩ Skip to picture
tag use cases
Script inclusion #
To load the lazy images as they enter the viewport, you need a lazy load script such as vanilla-lazyload which is a lightweight (2.5 kb gzipped), blazing-fast, configurable, SEO-friendly script that I created and I've been constantly improving since 2014.
Here is the simplest way to include it in your page.
<script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@18.0.0/dist/lazyload.min.js"></script>
Have a look at the documentation for more ways to include LazyLoad in your web pages, like using an async
script with auto-init, using RequireJS, using WebPack or Rollup.
LazyLoad initialization #
Including the vanilla-lazyload script gives you a LazyLoad
JS class you can use to load the images identified by the lazy
CSS class. You must create a LazyLoad
instance like this:
var lazyLoad = new LazyLoad({
// Your custom settings go here
});
Minimize layout reflow #
When using lazy loading, the images that haven't started loading collapse to 0
-height, only to grow when they'll have started loading. Layout reflowing would make your website janky, so it's a best practice to stabilize your layout by occupying the exact amount of space your images will take when loaded, before they start loading.
The universal solution to do that is to use the vertical padding trick, while in the future you'll be able to use the aspect-ratio
CSS directive to do it (as I'm writing it's landed in Chrome Canary only).
.image-wrapper {
width: 100%;
height: 0;
padding-bottom: 150%;
/* ☝️ image height / width * 100% */
position: relative;
}
.image {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
Here's also a useful SASS mixin to do that (source: CSS tricks).
@mixin aspect-ratio($width, $height) {
position: relative;
&:before {
display: block;
content: "";
width: 100%;
padding-top: ($height / $width) * 100%;
}
> .content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
More info in Sizing Fluid Image Containers with a Little CSS Padding Hack by Andy Shora.
Avoid "broken" images #
To avoid lazy images to appear as broken, even for a short amount of time, use CSS. Hide the images that still don't have neither an src
nor a srcset
attribute set.
img:not([src]):not([srcset]) {
visibility: hidden;
}
No polyfills required #
You might be tempted to add one or more polyfills to support Internet Explorer (yes, I named it and it's 2020). Don't do that, you don't need any. Let me tell you why:
-
Responsive images: Internet Explorer does not support responsive images, but you don't need to use a polyfill because IE gracefully degrades using the image specified in the
src
attribute. So choose an image that would appear nice on a regular desktop display, place in thesrc
attribute, and you're cool. -
IntersectionObserver: Internet Explorer does not support the
IntersectionObserver
API, which is used by vanilla-lazyload, but you don't need to provide a polyfill because vanilla-lazyload will detect the support for that API and, if missing, it will loads all images immediately. This leads to the same result as if no LazyLoad was ever used on the page, but it doesn't throw any errors.
That's cool, Internet Explorer is not being used by more than 5% of the users today, and Microsoft is silently replacing it with Edge via Windows Update.
Anyway if for some reason you want it to work in the same exact way on Internet Explorer, you can use the IntersectionObserver polyfill by including it before vanilla-lazyload.
<!-- Don't do this if you're not sure! Read above -->
<script src="https://cdn.jsdelivr.net/npm/intersection-observer@0.10.0/intersection-observer.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@18.0.0/dist/lazyload.min.js"></script>
Putting it all together #
For your convenience here's all the HTML, JS, and CSS code together.
<!-- Eagerly loaded,
above-the-fold only -->
<img
alt="Eager above"
src="220x280.jpg"
srcset="
220x280.jpg 220w,
440x560.jpg 440w
"
sizes="220px"
/>
<!-- Lazy loaded,
below-the-fold only -->
<img
alt="Lazy below"
class="lazy"
data-src="220x280.jpg"
data-srcset="220x280.jpg 220w,
440x560.jpg 440w"
data-sizes="220px"
/>
var lazyLoad = new LazyLoad({
elements_selector: ".lazy"
cancel_on_exit: true
});
/*
Images container to occupy space
when the images aren't loaded yet
*/
.image-wrapper {
width: 100%;
height: 0;
padding-bottom: 150%;
/* ☝️ image height / width * 100% */
position: relative;
}
.image {
position: absolute;
/* ...other positioning rules */
}
/*
Hide "broken" images before
they start loading
*/
img:not([src]):not([srcset]) {
visibility: hidden;
}
And that's it for the simple img
tag.
Picture tag use cases #
Until now, I wrote about the img
tag with the srcset
and sizes
attributes, which is the solution to the vast majority of the responsive images you might need to use on a website or web application. Now, in which cases should you use the picture
tag?
Different width/height ratio #
Use case: you need to show images with different width/height ratio depending on a media query. e.g. you want to show portrait images on mobile, vertical devices, landscape images on wider viewports, like tablets and computers.
Here's the code you're gonna need in this case. In order to have eagerly loaded images, just use the plain src
and srcset
attributes, without data-
prefix.
<picture>
<source
media="(min-width: 1024px)"
data-srcset="1024x576.jpg 1x,
2048x1152.jpg 2x"
/>
<source
media="(max-width: 1023px)"
data-srcset="640x960.jpg 1x,
1280x1920.jpg 2x"
/>
<img
class="lazy"
alt="Portrait or landscape"
data-src="1024x576.jpg"
/>
</picture>
Open the 👀 demo, then your browser's developer tools, then switch to the Network panel. You will see that it downloads only the image source corresponding to the first matching media query.
Load modern formats like WebP and Jpeg2000 #
Use case: you want browsers to choose the source to load a modern format like WebP and Jpeg2000 depending on its support for that format.
You need the source
tag and the type
attribute containing the MIME type of the images in the data-
/srcset
attribute.
<picture>
<source
type="image/jp2"
data-srcset="1024x576.jp2 1x,
2048x1152.jp2 2x"
/>
<source
type="image/webp"
data-srcset="1024x576.webp 1x,
2048x1152.webp 2x"
/>
<img
data-src="1024x576.jpg"
data-srcset="1024x576.jpg 1x,
2048x1152.jpg 2x"
data-sizes="1024px"
alt="Jp2, WebP or Jpg"
class="lazy"
/>
</picture>
Open the 👀 demo, then your browser's developer tools, then switch to the Network panel. You will see that it downloads only the image source corresponding to the first type that your browser supports.
💬 Isn't that markup too long for one image?
Yes, it is. And if you have money to invest in image optimization, there other ways to do that. Most of the cloud-based image servers in the market now automatically serve different image formats at the same URL. This means that you can request 1024x576.jpg
and you get a WebP or a Jpeg2000 accordingly. Cloudinary and Akamai Image & Video Manager do that, amongst others.
Image fidelity capping to 2x #
With the rise of very high density "super retina" displays in newest high-end devices such as the whole iPhone 12 and the whole Google Pixel lineups, capping image fidelity to 2x leads to a big improvement in terms of download speed, and no perceivable quality loss for your users.
👉 Here's a new best practice on how to do that.
Native lazyload #
You might have heard or read of native lazy-loading coming to the web. Cool, isn't it? As of May 2020, it's supported in Chrome, Firefox, Edge, Opera, and behind a flag in Safari.
So 100% browsers support isn't quite there, but in case you want to enable it on supported browsers, you could go for hybrid lazy-loading by setting the use_native
option of vanilla-lazyload to true
.
new LazyLoad({
use_native: true
});
You might miss these features #
If you go for native lazy-loading or hybrid lazyloading, you might miss some features that JS-driven lazy-loading grants.
- automatic classes application on events (
loading
,loaded
, etc.) - automatic retry loading images when the network failed and you're back online
- download cancelation when images exit the viewport while still loading, to prioritize the loading of new ones
- callbacks on events triggered (viewport enter/exit, loading started/finished, etc.)
Think about it carefully before switching to native lazy-loading. If you don't mind missing the above features, you're good to go.
Conclusions #
Here is a summary:
- Use vanilla-lazyload to load your lazy images.
- Don't load all the images lazily, just the ones below the fold
- Use the
img
for simple responsive images - Use the
picture
tag to- change your images width/height ratio at specific media queries
- conditionally serve your images in modern formats like WebP or Jpeg2000
- Don't use any polyfill if not strictly required
Happy lazy loading!
About this article #
If something is unclear or you think it could be improved, let me know in the comments or tweet me.
☕ If you've found this useful, you might want to express your gratitude by buying me a coffee. ☕
Useful resources #
- Responsive images in practice @ A List Apart
- Responsive images @ Mozilla Developer Network
- Responsive images in CSS @ CSS Tricks
- Responsive images community group website