Rethinking Image Formats

Adding images to a web page used to be straightforward. You’d add the img
tag to the HTML, set the src attribute to the appropriate URL and, hopefully, write some informative alt text. (You might also add some CSS, either inline or via a stylesheet.)
<img src="plot.png" alt="Scatter plot of age vs score. Line of best fit runs through the points, and an outlier can be seen at age 28, score 40." />
It’s slightly more complicated today, with monitor and browser technology changing the requirements, at least if you are using raster images (like JPEGS, PNGs and GIFs) and want things to look good for all your users. High density screens on smartphones have been popular for a while but 4k and 5k monitors are also becoming more affordable. To make text easy to read, these are often set to 200% scaling so that one measured pixel corresponds to 2 real pixels in each dimension. (For smartphones and tablets this scaling can even be 300%, though their true pixel counts are lower than those of 4k and 5k monitors.) A result of all this is that, for images not to look pixelated on these screens, they need twice as many pixels in each direction - that’s four times the number of pixels for a given image display size. So what can we do about this?
Using the srcset Attribute
Fortunately, browsers added the srcset attribute to make it easier for the developer to specify multiple images to use. The browser then picks the “best” option for a given user based on the information given in the srcset attribute and information the browser already has about the device on which the page is being viewed. The simplest way to utilise this attribute is to specify an image that is twice as large in the srcset property alongside a “2x” marker. By convention, we name the larger image the same as the smaller image, but with @2x in the name just before the extension:
<img src="plot.png" srcset="plot@2x.png 2x" alt="Scatter plot of age vs score. Line of best fit runs through the points, and an outlier can be seen at age 28, score 40.">
This tells the browser to serve the base image to users with “regular” screens and the larger image to those with scaled screens. You could also add a “3x” version here if you wanted, though that would require an image with nine times as many pixels as the base image. The actual file size in memory may not be nine times that of the base image due to the compression algorithms scaling well, but they’ll still be considerably bigger.
The shortcoming with the above syntax is that it’s not really targetting the right thing. It tells the browser to choose based only on scaling factors and not on the actual rendered image sizes. An image could be set to display at 600 “CSS” pixels on a wide screen, like a desktop monitor, and 300 CSS pixels on a narrower one, like a phone. For a phone with 2 times scaling the 600 pixel image would then look fine but the browser doesn’t inherently know that the 1200 pixel image is unnecessary. So it will (probably) load the 1200 pixel image, making page-load slower than necessary and potentially gobbling up more of the user’s mobile data than warranted.
The specification for srcset offers an alternative that seems to solve this issue: just directly list the widths of available images by specifying a number and the letter “w”:
<img
srcset="plot-small.png 300w, plot.png 600w, plot-large.png 1200w"
alt="Scatter plot of age vs score. Line of best fit runs through the points, and an outlier can be seen at age 28, score 40.">
If the browser knows what size the img
element will be rendered at, the sizes of the image options and the pixel density of the screen it can pick the best image for the job. The catch is that, at least when the browser sees the img
tag for the first time, it won’t know what size it will be rendered at unless we specifically tell it. We can do that using the sizes attribute on the img
element. Unfortunately, for responsive layouts this can get very messy and very confusing very quickly.
If you want to get into the nitty gritty of using srcset with sizes then there is a great article on CSS Tricks that goes into way more detail than we have space for here. Let’s, instead, look at alternative ways of reducing the burden of large images.
Using Vector Graphics
The solution that makes life easy… when it’s applicable. Instead of using a PNG (or JPEG), use an SVG - a scalable vector graphic.
Advantages of SVG
- Instead of storing data about the colours of millions of pixels, these files store a set of instruction for constructing an image. This is usually the perfect solution for company logos and most common chart types because they can be scaled however you like precisely because they’re just a list of instructions. No need to serve multiple images.
- They can be added to the page in a number of ways, including using a simple
img
tag. - With a bit of JavaScript they can be made interactive and they’re easy to animate.
Shortcomings of SVG
- They’re essentially useless for detailed images, like photography.
- Fonts may not be rendered properly when added through the src attribute of an image tag if that font isn’t already on the users system. A work-around for this is to open a vector-image editor and find the option for rendering text as paths. While this will likely increase the file size a bit and cause minor imperfections in text rendering, it may be more problematic that this adds an extra step in the workflow when the SVGs are generated programatically.
Illustrative example
Use the controls below to change between image formats and scaling to see the effect. It should be apparent that when you scale up a PNG or JPEG the image becomes more blurred and that the SVG, for the most part, remains crisp regardless of the scale-factor. (You may notice small artefacts with the SVG text when scaled up. These are seen because the characters are rendered using SVG paths rather than fonts, as described in the previous section.)
Using New Image Formats
Given the above, you may think the available image options for the web looks something like this:
- JPEG (with lossy compression) for images with (up to) millions of colours;
- PNG for images with large consistent blocks of colours (like logos) or images that require transparency;
- SVG for vector graphics;
- GIF for your favourite animated meme.
But for images that can’t be easily represented in vector format there are several newer raster image formats: JPEG XL, WebP, AVIF and HEIC (A.K.A. HEIF) that offer better compression (lossy and lossless) than PNG, JPEG and GIF. Of these new formats, only WebP and AVIF have meaningful browser support, but that support is actually very good: currently 95.4% for WebP and 93.5% for AVIF. In fact, you may think support is good enough for both formats to not need to provide a fallback. However, if you want to, you can use the picture
and source
elements to cover even more browsers:
<picture>
<source srcset="/images/home/whale-deep-dive-light-blue.webp 1x, /images/home/whale-deep-dive-light-blue@2x.webp 2x" type="image/webp">
<img src="/images/home/whale-deep-dive-light-blue.png" alt="Jumping Rivers' cartoon whale with Moon in background">
</picture>
In the above example we use the srcset attribute to provide two different sizes in the WebP format and the img
tag to provide a PNG fallback for older browsers (we assume users of older browsers aren’t using modern high-definition screens). The alt text also still needs to be included in the img
tag rather than moved into the source
or picture
tags.
When it comes to choosing between WebP and AVIF, WebP has marignally better browser support, but consensus is that AVIF offers better compression. This is maybe not surprising since it’s a much newer new format than WebP, which actually turns fifteen in 2025. The downside to that is that we have found support for AVIF in editing tools to be much lower than it is for WebP. That landscape is always changing, however. WebP has one other advantage over AVIF: it supports lossy images with transparency so if you need small image sizes and transparency it’s the only format in town.
Both WebP and AVIF support image animation but, as you will see in the next section, there’s another alternative for replacing our old friend the GIF.
The example below shows a 300-pixel-wide image of The Catalyst building in Newcastle, where Jumping Rivers is headquartered. You can choose between viewing a lossless PNG, lossless WebP, lossy JPEG, and a lossy WebP image. The two lossless formats should look the same, but the WebP image is about 20% smaller in file size than the PNG. The lossy images both have “medium” levels of compression so should be of roughly comparable quality, but not identical (since they use different compression algorithms). The lossy WebP image is only about one third the file size of the JPEG!
Using Videos Instead of GIFs
GIFs, particularly animated GIFs, have been a big part of internet culture. However, they are a very old format with large file sizes and poor colour gamuts.): they are limited to a max of just 256 different pixel colours. All modern browsers support video natively through the video
element and these offer much better compression and huge colour palettes.
<video src="assets/hex-dissolve.mp4" aria-label="Litmusverse hex sticker animation" autoplay="true" loop="true" muted="true"><video>
The aria-label attribute is used like the alt text of an img
element. The other attributes should be fairly self-explanatory: autoplay tells the browser to play the video automatically, loop to loop the video around back to the start when it finishes and muted not to play any sound. The latter is required because, thankfully, browsers will no longer autoplay videos with sound.
