Every image format exists because it solves a problem that previous formats didn't. Understanding which problem each one solves makes the choice obvious.
JPEG — 1992, Photographs
JPEG uses DCT (Discrete Cosine Transform) to discard visual information the human eye doesn't notice. It's lossy — once compressed, the original is gone.
When to use: Photographs, complex images with many colors.
When not to use: Text, logos, UI elements, anything with hard edges.
Quality setting guide:
- 85-95%: Near-lossless for storage, editorial use
- 75-85%: Web delivery, usually indistinguishable from original
- 60-75%: Aggressive compression, slight artifacts on close inspection
- Below 60%: Visible blocking artifacts
Repeated save/compress cycles degrade quality. Save source files in lossless formats.
PNG — 1996, Transparency
PNG uses lossless compression. What goes in comes out exactly. The killer feature: alpha channel (transparency).
When to use: Logos, icons, UI screenshots, anything needing transparency.
When not to use: Photographs (files get huge).
PNG-8 (256 colors) vs PNG-24 (full color): use PNG-8 for simple graphics with few colors, PNG-24 for everything else.
GIF — 1987, Animation
Still the dominant format for short looping animations, despite being 37 years old and technically terrible.
Limitations: 256 colors maximum (dithering helps but doesn't fix), no partial transparency, patent history.
When to use: Short UI animations, memes, reaction images.
Better alternatives: WebP (animated), APNG, CSS animations.
WebP — 2010, Google's Web-Optimized Format
WebP supports lossy and lossless compression, transparency, and animation. Roughly 25-35% smaller than equivalent JPEG at the same perceptual quality.
Browser support: All major browsers since 2020. Safe to use.
When to use: Web delivery of photographs and graphics. Drop-in replacement for JPEG/PNG with significant size savings.
Converting to WebP with Canvas:
async function toWebP(imageFile, quality = 0.85) {
const img = await loadImage(imageFile);
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
canvas.getContext("2d").drawImage(img, 0, 0);
return new Promise(resolve => {
canvas.toBlob(resolve, "image/webp", quality);
});
}
AVIF — 2019, The Successor
AVIF is derived from AV1 video codec. Roughly 50% smaller than JPEG at equivalent quality. Supports HDR, wide color gamut, transparency.
Browser support: Chrome 85+, Firefox 93+, Safari 16+. Good enough for production.
Encoding limitation: Canvas API's toBlob with "image/avif" has inconsistent browser support. Server-side encoding (sharp, libvips) is more reliable for generation. Reading/displaying AVIF in browsers works fine.
HEIC — 2017, Apple's iPhone Format
HEIC (High Efficiency Image Container) stores images at half the size of JPEG with equal quality. It's what your iPhone has been saving photos in since iOS 11.
The problem: Windows doesn't support it natively (without an optional codec pack). Most web services reject it.
Converting HEIC in the browser:
Canvas API can't decode HEIC. You need a library:
import heic2any from "heic2any";
async function heicToJpeg(heicFile) {
const blob = await heic2any({
blob: heicFile,
toType: "image/jpeg",
quality: 0.9,
});
return blob;
}
heic2any uses a WebAssembly decoder. It works but is slow for large files.
WEBP vs AVIF: The Real Comparison
| WebP | AVIF | |
|---|---|---|
| File size vs JPEG | ~25-35% smaller | ~40-50% smaller |
| Encoding speed | Fast | Slow |
| Browser support | Excellent | Good (not IE) |
| Canvas encoding | Supported | Inconsistent |
| Decoding speed | Fast | Slower |
Recommendation: Use WebP today. Consider AVIF for next-generation optimization where you control encoding (server-side).
The <picture> Element: Serving Multiple Formats
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Description" width="800" height="600">
</picture>
Browsers pick the first format they support. Fallback to JPEG for older browsers.
Detecting Format Support
async function supportsFormat(mimeType) {
const canvas = document.createElement("canvas");
canvas.width = canvas.height = 1;
const dataURL = canvas.toDataURL(mimeType);
return dataURL.startsWith(`data:${mimeType}`);
}
const webpSupport = await supportsFormat("image/webp");
const avifSupport = await supportsFormat("image/avif");
Practical Decision Tree
Is it a photograph?
→ Yes: WebP (or AVIF if you can encode server-side)
Does it need transparency?
→ Yes + photograph: WebP
→ Yes + graphics/logo: PNG (or WebP)
Is it a logo/icon with few colors?
→ PNG-8 or SVG
Is it an animation?
→ Short/simple: GIF (compatibility) or WebP animated
→ Complex: Video (MP4/WebM)
Is it from an iPhone?
→ Convert HEIC to JPEG or WebP before using on web
Client-side image format conversion is available at ToolZip — HEIC to JPG, WebP to JPG, PNG to WebP, and more.
Top comments (0)