Skip to content
Go back

Optimizing Web Assets, Part 3, Images

Table of Contents

Open Table of Contents

In what ways can image files be optimized?

There are several ways to speed up image loading (and processing):

  1. Lazy load and fetch priority
  2. Different file formats
  3. CSS Media Queries
  4. Progressive loading

I specified them in personal order of preference as well as difficulty of implementation, and it’s possible to combine them together.

Lazy Load and Fetch Priority

Lazy loading can be done with a simple img attribute that will tell browser to load image only when it is in a view. We can also specify decoding and fetch priority:

<img loading="lazy" decoding="async" fetchpriority="low" />

For (important) images that are in a view when user lands on a page, specify fetchpriority="high" to load it faster.

Choosing image file format

For static images on the web, I would almost exclusively choose WebP over PNG. It has great browser support, provides better file size than PNG and JPEG, and you can losslessly compress to WebP.

Convert PNG, JPEG, TIFF, or Y’CbCr to WebP

There is a great CLI tool to convert images to WebP file format: cwebp. It is easy to convert all your imported assets, supports lossless and resize:

for file in *.png; do
	cwebp "$file" -lossless -o ${file/.png/@2x.webp};
	cwebp "$file" -lossless -resize 140 140 -o ${file/.png/.webp};
done;

When you only got animated PNG or WebP, better use the original files, or convert them to video media.

CSS Media Queries

Image Size

We can use CSS Media Queries to tell browsers which images to load inside img or picture HTML tags. Google Lighthouse has “Properly size images” audit, which basically tells you to not load large images on small screens.

To fix this, use two different image files, and apply CSS Media Query like (min-width: 1280px) to load large image on desktop. Do not forget to adjust your CSS positioning if you are starting to use this on existing codebases.

Device Pixel Ratio

It is also possible to provide different images for different device pixel ratios. For smaller pixel ratios we need smaller image sizes to have same sharpness. When client’s screen has 2x pixel density, it’s better to serve them larger image, and 1x pixel density can have same sharpness with smaller size.

Use CSS Media Queries to display images based on user’s preferred theme.

Progressive Loading

Easiest way to achieve progressive loading is to use Progressive JPEG. If you don’t need that level of detail JPEG provides, resort to some JavaScript magic. Let’s take a look at how we can handle progressive loading:

  1. Create smaller image
  2. Load smaller image by default, apply blur to it
  3. Download original image
  4. Replace displayed image to loaded and remove blur

This can be done like this (some code is omitted for shortness):

<img src="/path/to/small.webp" data-progressive data-src="/path/to/og.webp" style="filter: blur(5px);" />
// on intersecting with view
delete img.dataset.progressive
const src = img.dataset.src
delete img.dataset.src
img.src = img.dataset.src
img.style.filter = '';

It requires more setup to use progressive loading and media queries together efficiently.

Techniques Combination

To use minimal viable combination of all techniques we need 5 files. If you get 1 PNG file as input:

  1. Convert to WebP at current resolution
  2. Convert to WebP at 2x less resolution
  3. Convert to WebP for mobile
  4. Convert to WebP for mobile at 2x less resolution
  5. Convert to WebP at very small resolution (low quality)
for file in *.png; do
	cwebp "$file" -lossless -o ${file/.png/@2x.webp};
	cwebp "$file" -lossless -resize 140 140 -o ${file/.png/webp};
	cwebp "$file" -lossless -resize 80 80 -o ${file/.png/@mob2x.webp};
	cwebp "$file" -lossless -resize 40 40 -o ${file/.png/@mob.webp};
	cwebp "$file" -lossless -o ${file/.png/@lq.webp};
done;

Share this post on:

Previous article
How to Rock a Systems Design Interview (Palantir)
Next article
Setting Up Laravel Octane with Swoole on MacOS