DEV Community

Luca Sammarco
Luca Sammarco

Posted on

How to Compress Images in the Browser With Canvas API (No Server Needed)

Most image compression tools upload your photos to a server, process them, and send back the result. But you can do the same thing entirely in the browser with the Canvas API — zero server, zero upload, zero privacy concerns.

Here's how.

The Core: Canvas + toBlob()

async function compressImage(file, quality = 0.8, format = 'image/webp') {
  return new Promise((resolve) => {
    const img = new Image();
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    img.onload = () => {
      canvas.width = img.width;
      canvas.height = img.height;
      ctx.drawImage(img, 0, 0);
      canvas.toBlob((blob) => resolve(blob), format, quality);
    };

    img.src = URL.createObjectURL(file);
  });
}
Enter fullscreen mode Exit fullscreen mode

That's it. 15 lines. Drop an image file in, get a compressed blob out.

What's actually happening

  1. new Image() creates an in-memory image element
  2. We draw it onto a <canvas> element (also in memory, never added to DOM)
  3. canvas.toBlob() re-encodes the image at the specified quality level
  4. The browser's native JPEG/WebP encoder handles the actual compression

The key insight: toBlob() with a quality parameter below 1.0 applies lossy compression. At 0.8 (80%), file sizes typically drop 70-90% with no visible quality difference on screen.

Adding WebP Conversion

Want to convert AND compress in one step? Just change the MIME type:

// JPEG → WebP conversion + compression
const webpBlob = await compressImage(jpegFile, 0.8, 'image/webp');
// WebP is typically 25-35% smaller than JPEG at the same quality
Enter fullscreen mode Exit fullscreen mode

Browser support for WebP encoding via Canvas: 97% globally (every browser except very old Safari).

Batch Processing

Real-world use needs batch support. Here's a practical version:

async function compressBatch(files, quality = 0.8) {
  const results = [];

  for (const file of files) {
    const compressed = await compressImage(file, quality);
    const savings = ((1 - compressed.size / file.size) * 100).toFixed(1);

    results.push({
      original: file.name,
      originalSize: file.size,
      compressedSize: compressed.size,
      savings: `${savings}%`,
      blob: compressed,
    });
  }

  return results;
}
Enter fullscreen mode Exit fullscreen mode

Real Results

I tested this on 100 images from different sources:

Source Avg Original Avg Compressed Savings
iPhone 15 photos 4.2 MB 420 KB 90%
Canva exports 1.8 MB 195 KB 89%
DSLR photos 8.7 MB 680 KB 92%
Screenshots 850 KB 95 KB 89%

At quality 0.8, the compressed images are visually indistinguishable from the originals on screen.

The Privacy Advantage

The entire operation happens in the browser's JavaScript runtime. The image data never touches a network request, never hits a server, never gets stored anywhere. This matters for:

  • Medical images that can't leave a hospital network
  • Legal documents with confidentiality requirements
  • Personal photos you don't want on someone else's server
  • Enterprise use where data stays on-premises by policy

Limitations

Canvas compression isn't perfect:

  • PNG optimization: Canvas toBlob('image/png') does lossless encoding but doesn't apply advanced PNG optimization (like pngquant). For PNG-specific compression, you need WebAssembly libraries.
  • AVIF: Canvas doesn't support AVIF encoding yet in most browsers. You need a WASM encoder.
  • Metadata: Canvas strips all EXIF data during re-encoding. Good for privacy, bad if you need to preserve camera settings.
  • Memory: Very large images (50MP+) can cause memory pressure in the browser tab.

Production-Ready Version

If you want a complete implementation with batch processing, WebP conversion, quality slider, ZIP download, and drag-and-drop UI, check out SammaPix — it's open source (MIT) and uses exactly this Canvas approach under the hood.

The full source is on GitHub if you want to see how we handle edge cases like HEIC input, Web Worker offloading, and progressive loading.

TL;DR

  • Canvas API + toBlob() = free image compression in the browser
  • Quality 0.8 = 90% file size reduction, no visible quality loss
  • Change MIME type to convert formats (JPEG → WebP)
  • Zero server, zero upload, zero privacy concerns
  • Works offline after page load

Top comments (0)