Web image optimization best practices techniques in 2022

Uploaded: 09 Nov 2021

As well as being vital for providing users with a quick to load experience, image optimisation is now set to play a key role in SEO with the increased importance of Google’s Core Web Vitals in determining ranking. Let's look at some of the things to think about when working with images in our application.

Core Web Vitals are the subset of Web Vitals that apply to all web pages, should be measured by all site owners, and will be surfaced across all Google tools. Each of the Core Web Vitals represents a distinct facet of the user experience, is measurable in the field, and reflects the real-world experience of a critical user-centric outcome.

The metrics that make up Core Web Vitals will evolve over time. The current set focuses on three aspects of the user experience—loading, interactivity, and visual stability. It is expected these will become ranking indicators in early 2022—and includes the following metrics:

  • Largest Contentful Paint (LCP) measures loading performance. To provide a good user experience, LCP should occur within 2.5 seconds of when the page first starts loading.

  • First Input Delay (FID) measures interactivity. To provide a good user experience, pages should have a FID of 100 milliseconds or less.

  • Cumulative Layout Shift (CLS) measures visual stability. To provide a good user experience, pages should maintain a CLS of 0.1. or less.

As part of your development process of adding new pages, periodic reviews, or changes to existing pages, the following checklist can be used to ensure you are providing users with the fastest, most positive experience possible in terms of image usage, which in turn will improve your Core Web Vitals score. This checklist is largely arranged in order of decreased to increased complexity.

With all these recommendations, tools like the Google Chrome lighthouse tool in the dev tools are your best friend to identify and prioritise improvements.

Are alt tags necessary?

Adding alternative text to images is first and foremost a principle of web accessibility. Visually impaired users using screen readers will be read an alt attribute to better understand an on-page image. Alt text will be displayed in place of an image if an image file cannot be loaded, and also provide better image context/descriptions to googlebot and other crawlers to help them index images appropriately.

However, there are generally two categories of image:

  • those that do not convey any additional meaning and are considered ‘decorative’ those that convey meaning and add to the understanding of a page
  • decorative images, which do not need to be announced by the screen reader, so adding role=”presentation” or an empty alt attribute (the latter has marginally better support) are valid methods of dealing with these types of images, and will not be announced to screenreader users.

Further reading: https://www.w3.org/WAI/tutorials/images/decorative/

Shoud images have explicit widths and heights?

In 2022, definitely! Historically, setting width and height attributes again HTML images was an anti-pattern. However recently they have become an important part of avoiding layout shift. Layout shifts can be frustrating to users as elements could move around, possibly making the page appear visually jarring.

Avoiding large layout shifts is essential in delivering a smooth and streamlined experience to your visitors and improving the Google page experience metrics.

Image and/or video elements that aren't explicitly declared with height and width attributes are usually re-sized using CSS (either on the image itself or the parent container). When this happens, the browser can only determine their dimensions and allocate space for them once it starts downloading the unsized assets.

One question many people have is what height/width attributes to set, as the dimensions of an image may change depending on the viewport. In reality, the browsers do not care about the actual values of the width and height - they actually use them to compute an aspect ratio:

<!-- alt tag assumes this is a presentational image -->
<img
  src="flowers_300x300.jpg"
  alt=""
  width="160"
  height="90" />
<!-- browser still computes the aspect ratio of 16:9 -->

As long as the aspect ratio is consistent across viewports, you could in theory set the width and height to be anything that still results in the same aspect ration being calculated:

<!-- alt tag assumes this is a presentational image -->
<img
  src="flowers_300x300.jpg"
  alt=""
  width="320"
  height="180" />
<!-- browser still computes the aspect ratio of 16:9 -->
Recommendation
My personal approach is to just use the width and height of the image size that is rendered on a desktop screen.

CSS typically sets images to a maximum width of 100%, so an image resizes itself to be a maximum of 100% of it’s container:

img {max-width: 100%};

However, as we have now explicitly set the height on the image tag, then we are not overriding that (only the width) and the result would be a stretched or squashed image, as we have no longer maintained the aspect ratio of the image. The fix is fairly trivial:

img {
  max-width: 100%;
  height: auto;
};
Good to know!
When using srcset or picture (see below sections) - similar to the alt tag, you should only need to put the width and height attributes on the main img element that is responsible to loading the correct image from the sourceset - similar to the alt tag.

When is the CSS property aspect-ratio appropriate?

The CSS aspect-ratio property is not really needed for html image elements, since it’s computed by the browser based on the height and width attributes. However there are use cases for it, such as when a page image is set with a CSS background image on a div element, where it can helps us to size elements consistently, so the ratio of an element stays the same as it grows or shrinks.

What base image format is best for my website image?

We have a plethora of image formats used on the web that we can take advantage of. All too often, PNGs are used rather than JP(e)Gs which leads to physical image size being much larger than it needs to be. Progressive enhancement to provide WebP files (another image format) is discussed within this document.

JP(e)G

This is a lossy compression specification that takes advantage of human perception. It can achieve compression ratios of 1:10 without any perceivable difference in quality. Beyond this, the compression artefacts become more prominent. Because JPG compression works by averaging out colours of nearby pixels, JPG images are best suited for photographic type imagery where the variations in colour and intensity are smooth. However, if an image contains text or lines, where a sharp contrast between adjacent pixels is desired to highlight the proper shape, this lossy compression technique does not yield good results. Transparency is not supported.

PNG

This is a lossless image format - no data is lost during compression and no compression artefacts are introduced in the image. For this reason, a PNG image would retain higher quality than an image than JPEG and would look a lot sharper, it would also occupy more space on the disk. This makes it unsuitable for storing or transferring high-resolution digital photographs but a great choice for images with text, logos and shapes with sharp edges or images needing transparency.

GIF

Like PNG, GIF is also a lossless image format. It was favoured over PNG for simple graphics in websites in its early days because the support of PNG was still growing. Given that PNG is now supported across all major devices and that PNG compression is about 5–25% better than GIF compression, GIF images are now mainly used only if the image contains animations.

SVG

SVG is an image file format created specifically for designing two-dimensional vector and vector-raster graphics for websites. SVG supports animation, transparency, gradients, and is easily scalable without losing quality. It is generally a good choice for simple line art like icons.

Should I properly size and compress website images?

For sure - sometimes we are supplied assets which are obviously too big and can be resized down. As a general rule of thumb, an image should not be more than twice as wide as the maximum size it is rendered on within a page. (Why twice as wide? - to enable crisp images on retina / high density displays, where pixels can be accommodated more densely on these displays).

In terms of compression, it is often overlooked as we assume any image provided to us by third parties has been compressed appropriately already. Instead, try to compress the image further using desktop design packages or online tools such as tinypng, which use lossy compression and tinyjpg. Obviously, ensure that the visual differences in compression do not compromise the quality and user perception of the image.

Recommendation
It is always a good idea to have a copy of any uncompressed, original sized image within source control which can be used in case you need to regenerate at different sizes, tweak compression settings etc.

What is WebP? Should I use it?

WebP is an image format employing both lossy and lossless compression, and supports animation and alpha transparency. Developed by Google, it is designed to create files that are smaller for the same quality, or of higher quality for the same size, than JPG, PNG, and GIF image formats.

Lossy or lossless?
As webp supports both lossy and lossless compression, ensure you select the correct option for lossy or lossless. When converting PNG images, the recommended lossless setting should be true, and false for JPG. When converting JPG images, begin with a quality of 80 and then fine tune the quality to get an acceptable blend of quality vs size.

Not all browsers will support webp, so treat this as a progressive enhancement, which can be implemented in code as follows:

<picture>
  <source type="image/webp" srcset="/assets/images/flowers.webp" />
  <img src="/assets/images/flowers.png" alt="" />
</picture>

Can I use WebP with CSS background images?

Webp support for CSS background images is slightly different. Unlike the element in HTML which falls back gracefully to the element in all browsers, CSS doesn’t provide a built-in solution for fallback images that’s optimal. Solutions such as multiple backgrounds end up downloading both resources in some cases, which is a big optimisation red flag. The solution lies in feature detection. A 3kb modernizr custom build can be added to an application which will detect webp support and add a class on the body to notify whether webp is supported or not. It’s then a fairly simple case in the css to provide fallback:

.no-webp .hero-banner {
  background-image: url("image.jpg");
}
.webp .hero-banner{
  background-image: url("image.webp");
}

What is native image lazy loading?

Lazy loading of resources has historically been done with javascript implementations, with the aim of deferring resources until near the point of time they are needed. If a page has loads of content and images, rather than loading all the images upon page load, the idea was to load them when the user scrolled close to them. The benefits of this are obvious - a user might never scroll down to actually view an image, yet their browser has needlessly downloaded them anyway. Now, we have native lazy loading across most browsers. Browsers that do not support native lazy loading will just load the images normally on page load.

The loading attribute allows a browser to defer loading offscreen images and iframes until users scroll near them. loading supports three values:

  • lazy: is a good candidate for lazy loading.
  • eager: is not a good candidate for lazy loading. Load right away.
  • auto: browser will determine whether or not to lazily load.
<img
  src="flowers_300x300.jpg"
  loading="lazy"
  alt="" />

Should I lazy load all images?

Do not simply add lazy loading to all images. There is some evidence that images above the fold which are set to lazy loading will take a performance hit. This begs the question; what is ‘the fold’? The fold is likely be different depending on the user’s viewport. My personal approach is to only add lazy loading images which are below the fold across all common viewports. Of course, using non native javascript implementations could help work out what the fold is, but native lazy loading reduces complexity so is the method I personally recommend.

Browsers are likely to use a number of factors to determine when to load lazy content - not just when the user scrolls close to them, but also factors like connection speed, to ensure images are loaded by the time they are needed.

Beware that, after changes to a webpage, what might previously have been below the fold is now above the fold, and vice-versa. Review lazy loaded images periodically to review they are still fit for purpose.

How can I serve retina images separately?

Let’s assume we have a responsive layout with an image that is 300px x 300px on a mobile view and 100px x 100px on a desktop view (in a 3 column grid on desktop). Taking everything that we have learned so far, we should probably ensure the base image is 600px x 600px (the largest size the image is displayed at multiplied for two to provide a high density version for retina displays).

By itself (without taking into account webp variations), the code is fairly simple - we have a srcset attribute, which stipulates which image to load for standard and retina displays, plus a regular src attribute for any browsers that do not support srcset:

<img
  srcset="flowers_300x300.jpg 1x, flowers_600x600.jpg 2x"
  src="flowers_300x300.jpg"
  alt="" />

The above code will mean that on browser with a non-retina desktop display, the smaller 300x300 image will be requested rather than a larger 600x600 image, which will increase performance on those devices - but the retina device will still download a higher quality image to account for the increased pixel density.

As we have learned though, we should probably also cater for webp based images too. With webp support too, the effort required is a little more considerable as we now have webp variations of each size to contend with:

<picture>
  <source
    type="image/webp"
    srcset="flowers_300x300.webp 1x, flowers_600x600.webp 2x">
  <source
    type="image/jpg"
    srcset="flowers_300x300.jpg 1x, flowers_600x600.jpg 2x">
  <img
    alt=""
    src="flowers_300x300.jpg"
    srcset="flowers_300x300.jpg 1x, flowers_600x600.jpg 2x" />
</picture>

This is the best of both worlds, albeit at the expense of even more image variations. However, there is a still a problem - fully responsive images:

  • on a non-retina mobile display, we are downloading a 600x600px image when we only really need a 300x300px image!
  • on a non-retina desktop display, we are downloading a 600x600px image when we only really need a 100x100px image! How do we handle this? See the next section to find out.

How can I serve fully responsive images?

The answer to this is probably yes (at a larger development and maintenance cost), so may be best to implement for the largest images.

Clarification
When we talk about fully responsive images here, we assume we are talking about benefits in performance i.e serving different size images at the same aspect ratio, rather than full design control, where we want to serve different images entirely.

Using srcset with sizes, we’re still serving the same image at multiple sizes, however we’re giving the browser more information so that it can adapt based on both pixel density and layout size.

Simple example

Excluding the webp variations temporarily for brevity, a simple implementation is as follows:

<img
  alt=""
  srcset="flowers_300x300.jpg 300w, flowers_600x600.jpg 600w"
  sizes="
    (max-width: 500px) calc(100vw - 48px),
    (max-width: 1023px) calc(0.333 * (100vw - 48px)),
    calc(0.333 * (100vw - 48px)),
  src="flowers_300x300.jpg" />

We’re still providing multiple copies of the same image and letting the browser pick the most appropriate one. But instead of annotating them with a pixel density (x) we’re labelling them with their resource width, using w descriptors. So if flowers_300x300 is 300×300, we label it as 300w.

Using srcset with width (w) descriptors like this means that it will need to be paired with the sizes attribute so that the browser will know how large of a space the image will be displaying in. Without this information, browsers can’t make smart choices.

In the above example:

  • At the smallest size: the image is full width, but is in a div which has 24px of horizontal padding each size, so the sizes attribute should be calc(100vw - 2rem)
  • At the medium size: we have a grid of images (three per row), and the grid has 24px of horizontal padding each side, so we have calc(0.333 * (100vw - 48px))
  • At the largest size: we still have a grid of images (three per row), but the grid has more horizontal padding each side (48px), so have calc(0.333 * (100vw - 48px))

There is a great bookmarklet available that can debug common problems with your sizes attribute (and more).

The great thing about this is that the browser caters for pixel density automatically - you’re likely to see a larger image load for retina screens than for standard density, to cater for the increased device pixel ratio.

Creating sizes attributes can get tricky and time consuming. The sizes attribute describes the width that the image will display within the layout of your specific site, meaning it is closely tied to your site CSS structure. The width that images render at is layout dependant, and not just viewport dependent! This means that you need to take into account what the margin and padding around your images is at every breakpoint to compute your sizes on a case by case basis. This can also be very brittle - what happens when your generic grid component margins/paddings are changed is that your sizes attribute becomes invalid and your browser does not serve the image you expect it to serve?

Responsive images, with webp

When adding additional webp support, the code becomes more complex:

<picture>
  <!-- load webp in different sizes if browser supports it -->
  <source
    type="image/webp"
    srcset="flowers_300x300.webp 300w, flowers_600x600.webp 600w"
    sizes="
      (max-width: 500px) calc(100vw - 48px),
      (max-width: 1023px) calc(0.333 * (100vw - 48px)),
      calc(0.333 * (100vw - 48px))">
  <img
    srcset="flowers_300x300.jpg 300w, flowers_600x600.jpg 600w",
    sizes="
      (max-width: 500px) calc(100vw - 48px),
      (max-width: 1023px) calc(0.333 * (100vw - 48px)),
      calc(0.333 * (100vw - 48px))"
      src="flowers_300x300.jpg"
      alt="">
</picture>

Wrap up

Thanks for reading, I hope you've now learned some of the techniques to help with optimizing your website images so you can take advantage of faster load times, user experience, and hopefully more leads.

I hope you've learned something!

Note: I try to make these articles as error free as possible, but if you notice something that's not quite right, please contact me!