JPEG, PNG and GIF work everywhere, but newer codecs give the same quality in fewer bytes. WebP stores a VP8 video key frame (or a lossless bitstream) in a RIFF container; Google measures it 25-34% smaller than JPEG, and 26% smaller than PNG when lossless. AVIF stores an AV1 key frame in a HEIF container and usually beats WebP, at the cost of slower encoding. JPEG XL (ISO/IEC 18181) was designed as a still-image codec from the start: it decodes progressively, supports HDR and wide color gamuts (as AVIF does), and can losslessly repack an existing JPEG about 20% smaller.
| Format | Compression | Alpha | Animation | Support, September 2026 |
|---|---|---|---|---|
| JPEG | Lossy | No | No | All browsers |
| PNG | Lossless | Yes | APNG | All browsers |
| GIF | Lossless, 256 colors | 1-bit | Yes | All browsers |
| WebP | Lossy or lossless | Yes | Yes | Baseline since 2020 (Safari 14 10 ) |
| AVIF | Lossy or lossless | Yes | Yes | Baseline since January 2024 (Edge 121) |
| JPEG XL | Lossy or lossless | Yes | Yes | Safari 17+ (stills only); Chrome 1 , Firefox 555 behind flags |
JPEG XL has had a bumpy ride. Chrome removed its flag-only decoder in Chrome 110 (February 2023); Safari 17 (September 2023) shipped still-image support; Chrome 145 (February 2026) brought it back behind a flag with jxl-rs, a memory-safe Rust decoder, and Chrome Platform Status lists default enablement as proposed. Firefox enables it only in Nightly. Until more engines ship it, treat JPEG XL as an optional extra source.
Serve new formats with a fallback chain. The browser takes the first <source> whose type it supports, without downloading the others, so list the smallest format first:
<picture>
<source type="image/avif" srcset="elva-480.avif 480w, elva-800.avif 800w, elva-1200.avif 1200w">
<source type="image/webp" srcset="elva-480.webp 480w, elva-800.webp 800w, elva-1200.webp 1200w">
<img src="elva-800.jpg" srcset="elva-480.jpg 480w, elva-800.jpg 800w, elva-1200.jpg 1200w"
sizes="(width <= 600px) 100vw, 600px" width="800" height="533" alt="Girl in a fairy costume">
</picture>Generate the files at build time with sharp 32,725 (github.com/lovell/sharp (https://github.com/lovell/sharp 32,725 ), Apache-2.0, npm 2,036 install sharp). It wraps libvips, which processes images on demand in small regions across threads rather than loading every pixel into memory; sharp's README puts resizing at typically 4x-5x faster than ImageMagick 3,122 .
import sharp from 'sharp';
import { stat } from 'node:fs/promises';
const source = 'elva-fairy-original.jpg'; // 2592 x 1728 camera JPEG
const formats = {
jpg: (img) => img.jpeg({ quality: 80, mozjpeg: true }),
webp: (img) => img.webp({ quality: 75 }),
avif: (img) => img.avif({ quality: 50, effort: 4 }),
};
for (const width of [480, 800, 1200]) {
const row = [`${width}w`.padEnd(6)];
for (const [ext, encode] of Object.entries(formats)) {
const file = `elva-${width}.${ext}`;
await encode(sharp(source).resize({ width })).toFile(file);
row.push(`${ext} ${((await stat(file)).size / 1024).toFixed(0).padStart(4)} KB`);
}
console.log(row.join(' '));
}
console.log('JPEG XL output available:', sharp.format.jxl.output.file);480w jpg 25 KB webp 17 KB avif 12 KB 800w jpg 58 KB webp 34 KB avif 25 KB 1200w jpg 109 KB webp 59 KB avif 48 KB JPEG XL output available: false
Here AVIF is about half the size of MozJPEG 5,724 , with WebP in between. Quality numbers differ between codecs, so tune by eye (squoosh.app 33,602 compares side by side). sharp's prebuilt binaries omit JPEG XL; use cjxl from libjxl 3,686 (github.com/libjxl/libjxl (https://github.com/libjxl/libjxl 3,686 ), BSD-3-Clause). To resize on request instead, run imgproxy (Apache-2.0) or Thumbor (MIT), or pay for an image CDN such as Cloudinary 976 or imgix.