Skip to main content

Compare how different image formats load on the web

4 min read

Older Article

This article was published 4 years ago. Some information may be outdated or no longer applicable.

Image formats on the web

According to the Media chapter in the Web Almanac project, 99.9% of pages fire at least one request for an image resource. Almost every website on the planet loads an image of some sort. And because images carry information faster than text (“a picture is worth a thousand words”), loading them efficiently matters a great deal.

The web has cycled through a number of image formats over the years. As of last year (per the Web Almanac), JPEG still dominates, followed by PNG, gif, WebP, svg, and others. Here’s the interesting bit: WebP is a decade old, only reached full browser support in 2020, and still accounts for roughly 7% of all image formats in use.

Jake Archibald wrote an excellent article on AVIF walking through the ins and outs of that format. I also wrote a piece covering the pros and cons.

A tool to visualise loading behaviour

I wanted to build a tool that lets me see the perceived load performance of different image formats side by side.

The tool is simple. It takes an image, splits it into three equal parts, and loads each part using whichever image format you pick in the UI.

How does it pull this off automatically? I’m using Cloudinary behind the scenes. Cloudinary can crop images using x and y coordinates and serve different formats via the f_ URL modifier.

Two helper functions handle the core logic. The first grabs the image height based on the input (currently set in the source code). To avoid making repeat requests for the same measurement, the height gets stashed in localStorage, keyed off the src.

const getImageHeight = (src) => {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.src = src;
    img.onload = function () {
      localStorage.setItem(src, this.height);
      resolve(this.height);
    };
    img.onerror = reject;
  });
};

The second helper loads the images in the format the user selected:

const loadImage = (src, location, done) => {
  const img = new Image();
  img.src = src;
  img.onload = function () {
    position++;
    done(location, position);
  };
  document.getElementById(location).appendChild(img);
};

The position variable starts at 0 and feeds into a ranking map. This map prints a load-time ranking in the UI (see the sample videos below):

let position = 0;
const ranking = new Map();
ranking.set(1, '🥇');
ranking.set(2, '🥈');
ranking.set(3, '🥉');

Here’s how loadImage gets called:

loadImage(
  `https://res.cloudinary.com/tamas-demo/image/upload/w_${width}/c_crop,w_${splitWidth},h_${height},y_0,g_west/f_${format}/${publicId}`,
  index,
  (location, position) => {
    document.getElementById(`format-${location}`).textContent += `${ranking.get(
      position
    )}`;
  }
);

The src parameter includes the total width, a splitWidth for each section, and the y axis set to 0. The done callback injects the image into the DOM and stamps a ranking plus a “loading complete” checkmark (drawn from the map above).

The second and third images use x_${splitWidth} and x_${splitWidth * 2} respectively. For a 1200-pixel-wide image, we’d create three 400-pixel slices and crop them with g_west, x_400 and g_west,x_800.

In the example below, AVIF, Progressive JPEG, and PNG are selected. The network connection is throttled to “Slow 3G” via Chrome’s DevTools.

As expected, the AVIF file loads first. But because of how this format works, the entire file arrives before anything renders. Compare that with PNG and JPEG. With progressive JPEG, a blurry layer appears first, then sharpens layer by layer until the image is crisp.

In the second example we see WebP, AVIF, and JPEG-XL. (You’ll need to enable JPEG-XL support manually: chrome://flags/#enable-jxl.) AVIF loads first again, then JPEG-XL, then WebP. Worth noting: JXL has multiple decoding speeds (cjxl --faster_decoding=3 would make it even faster). For perceived load time, JXL wins against the other two.

Want to learn more about JXL? Read Time for Next-Gen Codecs to Dethrone JPEG. I’m genuinely excited about JXL because it’s a true image format that’ll have a real impact on the web. Both WebP and AVIF are derived from video codecs and carry some shortcomings because of that.

If you’re interested in this tool, get in touch. I don’t plan on making it public right now. I use it in web performance workshops to show how different image formats behave under various conditions and what trade-offs each one carries.