In this article, I will share 8 techniques for optimizing image loading that reduce the required network bandwidth and processor load when displayed on the screen. Here are some examples of annotated HTML to make it easier for you to reproduce. Some techniques have been known for a long time, and some have appeared relatively recently. Ideally, your favorite web document publishing mechanism (such as a CMS, static site generator, or web application framework) should do all of this out of the box.
Collectively, the techniques optimize all elements of Google Core Web Vitals by:
- minimizing major content problems ( Largest Contentful Paint (LCP) ) through size reduction, caching, and lazy loading;
- preservation of zero cumulative layout shift ( Cumulative Layout Shift (CLS) );
- reducing the delay of the first input ( First Input Delay (FID) ) by reducing the processor consumption (for the main thread of execution).
To see all the techniques in action, take a look at the source code for loading this image:
https://www.industrialempathy.com/img/remote/ZiClJf.jpg
<img loading="lazy" decoding="async" style="background-size: cover; background-image: none;" src="/img/remote/ZiClJf.avif" alt="Sample image illustrating the techniques outlined in this post." width="4032" height="2268">
Optimization techniques
Responsive layout
This straightforward technique allows the image to occupy the available horizontal space while maintaining the aspect ratio. In 2020, browsers have learned to reserve the correct amount of vertical space for an image before it loads if the element
img
contains attributes
width
and
height
. This avoids the cumulative shift of the layout.
<style>
img {
max-width: 100%;
height: auto;
}
</style>
<!-- Providing width and height is more important than ever. -->
<img height="853" width="1280" … />
Lazy rendering
The second technique is more complicated. The new CSS attribute
content-visibility: auto
tells the browser not to think about placing the image until it's ready. This approach has several advantages, the main one of which is that until the browser receives a blurred placeholder image or the image itself, it will not decode it, saving processor resources.
No longer need contain-intrinsic-size
An earlier version of the article explained how to
contain-intrinsic-size
avoid the CLS effect when using
content-visibility: auto
. But in Chromium 88 this is no longer necessary in the case of images for which
width
and
height
. As of January 27, 2021,
content-visibility: auto
not yet implemented in other browser engines , they are likely to follow Chromium's lead. So yeah, it's much easier now!
<style>
/* This probably only makes sense for images within the main scrollable area of your page. */
main img {
/* Only render when in viewport */
content-visibility: auto;
}
</style>
AVIF
AVIF is the most recent graphics format that has received support in browsers. It is now supported in Chromium and by flag in Firefox. Safari doesn't work with it yet, but since Apple is part of the group that developed the format, this browser should also support AVIF in the future.
This format is remarkable in that it is vastly superior to JPEG. And this compares favorably with the WebP format, the images in which are not always smaller than JPEG and which can increase resource consumption due to the lack of support for progressive loading.
To implement a progressive extension for AVIF, you can use
picture
.
The element is actually
img
nested in
picture
... This can be confusing, because it is
img
sometimes called a fallback solution for browsers that do not support
picture
, but in fact this element only helps with the choice
src
, and it does not have its own layout. The element
img
will be drawn , and you will apply the style to it.
Until recently, it was quite difficult to implement AVIF images on the server side, but recent versions of libraries like sharp made this task much easier.
<picture>
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.avif 1920w,
/img/Z1s3TKV-1280w.avif 1280w,
/img/Z1s3TKV-640w.avif 640w,
/img/Z1s3TKV-320w.avif 320w
"
type="image/avif"
/>
<!-- snip lots of other stuff -->
<img />
</picture>
Loading the correct number of pixels
The above code has attributes
srcset
and
sizes
. They use a selector
w
to tell the browser which URL to take based on the physical number of pixels it takes to render the image on a particular device. This amount depends on the width of the image, which is calculated based on the attribute
sizes
(which is an expression from the media query).
This ensures that the browser will always load the smallest image possible, providing the best quality on a particular device. Or he can select the smallest image if the user has enabled data save mode.
Fallback solution
For browsers that only support older image formats, you can
srcset
provide more raw elements with the help of:
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.webp 1920w,
/img/Z1s3TKV-1280w.webp 1280w,
/img/Z1s3TKV-640w.webp 640w,
/img/Z1s3TKV-320w.webp 320w
"
type="image/webp"
/>
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.jpg 1920w,
/img/Z1s3TKV-1280w.jpg 1280w,
/img/Z1s3TKV-640w.jpg 640w,
/img/Z1s3TKV-320w.jpg 320w
"
type="image/jpeg"
/>
Caching and immutable URLs
Embed in the image URL a hash of the number of bytes that the image occupies. In the example above, I did it with
Z1s3TKV
. When you change the image, the URL will also change, which means you can apply infinite caching of images. Caching headers should look like
cache-control: public,max-age=31536000,immutable
.
immutable
Is a semantically correct meaning
cache-control
, but it has little browser support today (I'm looking at you, Chrome).
max-age=31536000
- fallback caching method throughout the year.
public
is needed for your CDN to cache the image and deliver it from the network edge. But this approach can only be used if it does not violate your privacy policies.
Lazy loading
By adding
loading=«lazy»
to the element,
img
we tell the browser to start fetching the image only when it is ready to be rendered.
<img loading="lazy" … />
Asynchronous decryption
By adding
decoding=«async»
to the element,
img
we allow the browser to decrypt the image outside the main stream so that this procedure does not interfere with the user. There should be no noticeable flaws in this solution, except that it is not always applicable by default in older browsers.
<img decoding="async" … />
Blurred stub
A fuzzy stub is an inline image that gives the user some idea of a full-fledged picture that will be loaded later, without transferring data over the network.
https://www.industrialempathy.com/img/blurry.svg
A few implementation notes:
- The stub is inlined like
background-image
images. This technique lets you drop the second HTML element by literally hiding the stub when the main image is loaded, no JavaScript is needed. - The main image data URI is wrapped in the SVG image data URI. This is done because the blur is done at the SVG level and not using a CSS filter. That is, the blur is done once for each image when rasterized by SVG, not for each layout. This saves processor resources.
<img
style="
…
background-size: cover;
background-image:
url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns=\'http%3A//www.w3.org/2000/svg\'
xmlns%3Axlink=\'http%3A//www.w3.org/1999/xlink\' viewBox=\'0 0 1280 853\'%3E%3Cfilter id=\'b\' color-interpolation-filters=\'sRGB\'%3E%3CfeGaussianBlur stdDeviation=\'.5\'%3E%3C/feGaussianBlur%3E%3CfeComponentTransfer%3E%3CfeFuncA type=\'discrete\' tableValues=\'1 1\'%3E%3C/feFuncA%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter=\'url(%23b)\' x=\'0\' y=\'0\' height=\'100%25\' width=\'100%25\'
xlink%3Ahref=\'data%3Aimage/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAGCAIAAACepSOSAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAs0lEQVQI1wGoAFf/AImSoJSer5yjs52ktp2luJuluKOpuJefsoCNowB+kKaOm66grL+krsCnsMGrt8m1u8mzt8OVoLIAhJqzjZ2tnLLLnLHJp7fNmpyjqbPCqLrRjqO7AIeUn5ultaWtt56msaSnroZyY4mBgLq7wY6TmwCRfk2Pf1uzm2WulV+xmV6rmGyQfFm3nWSBcEIAfm46jX1FkH5Djn5AmodGo49MopBLlIRBfG8yj/dfjF5frTUAAAAASUVORK5CYII=\'%3E%3C/image%3E%3C/svg%3E');
"
…
/>
(Optional) JavaScript optimization
Browsers may be forced to rasterize the blurry stub even if the image is already loaded. The problem can be solved by removing the rasterization at boot. In addition, if your image has transparent areas, then this optimization becomes mandatory, otherwise a stub will show through the image.
<sript>
document.body.addEventListener(
"load",
(e) => {
if (e.target.tagName != "IMG") {
return;
}
// Remove the blurry placeholder.
e.target.style.backgroundImage = "none";
},
/* capture */ true
);
</sript>
Additionally
A useful tool that implements all the described optimizations: eleventy-high-performance-blog