Made in the Browser: Image Compression

How the Vayce Image Compressor uses the browser’s own APIs to shrink images locally private, and entirely offline.

Sun Oct 19 2025 • 6 min read

A screenshot of vayce image compression tool

Every web creator knows this moment: you have a folder of photos, all a bit too heavy for the web. You search for a “free image compressor,” drag your files in - and watch them upload somewhere.

It feels normal. But it shouldn’t.

The Vayce Image Compressor does the same job without sending a single byte anywhere. No servers. No uploads. No middlemen. Just your browser doing what it’s already capable of.


Why Compress Locally?

It’s not just about privacy - though that’s reason enough. It’s about responsiveness.

When everything runs inside the browser:

  • There’s no network delay. You see results as you work.
  • There’s no upload or download cost. You stay in control of your files.
  • And there’s no dependency on someone else’s infrastructure.

It’s the same reason we prefer typing in a local editor instead of a laggy web form. Local operations feel human-speed - instant, quiet, and reliable.


The Architecture in Plain Language

Here’s the shape of the app in one sentence:

A worker pool decodes, resizes, and re-encodes each image using the browser’s built-in codecs to do the compression work. These are the same codecs your browser uses when saving images - fast and reliable.

Each part of that sentence represents a hidden piece of the web:

  1. Web Workers handle the heavy lifting so the interface never freezes.
  2. createImageBitmap() decodes image data off the main thread.
  3. OffscreenCanvas draws and re-encodes the image, optionally resizing it.
  4. convertToBlob() turns the result back into a downloadable file.

The rest is orchestration - dispatching tasks, tracking progress, and cleaning up when you’re done.

Let’s unpack those parts.


Parallel Work Without the Chaos

When you load a handful of images, the app quietly spins up a pool of workers - usually two to four, depending on your CPU.

Each worker lives in its own thread, isolated from the UI. When a new file arrives, it’s sent to whichever worker is free. If all workers are busy, the task waits in a queue. When a worker finishes, it pulls the next task and tries again - safely and without race conditions.

It’s a small system, but it mimics a production-grade job scheduler: always balanced, never blocking, never overwhelming your device.

This means you can drag in twenty images and keep adjusting settings without any lag. The browser multitasks gracefully - a few workers crunch numbers while one keeps the interface smooth.


Decoding the Image

The worker starts by turning the uploaded file into an ArrayBuffer, then into a Blob. From there, it calls:

const bitmap = await createImageBitmap(blob);

That one line does something remarkable - it decodes the image without touching the DOM.

No <img> elements, no hidden canvases, no reflows. Just raw pixel data in memory, ready to manipulate.


Drawing, Resizing, and Re-encoding

Once decoded, the image is drawn onto an OffscreenCanvas:

const canvas = new OffscreenCanvas(targetWidth, targetHeight);
const ctx = canvas.getContext('2d');
ctx.drawImage(bitmap, 0, 0, targetWidth, targetHeight);

This is the transformation stage. If you’ve set a max width, the image scales down while keeping its aspect ratio. If not, it stays the same size.

Then comes the crucial part - re-encoding:

const outBlob = await canvas.convertToBlob({
    type: mime,
    quality: mime === 'image/png' ? undefined : quality
});

Here, the browser’s built-in codec does the compression work. For JPEG and WebP, you can control the quality between 0 and 1. For PNG, quality is ignored - browsers always export lossless PNGs.

The result is a clean, ready-to-download blob. No libraries, no dependencies, no network.


When Workers Aren’t Available

Not every browser supports OffscreenCanvas inside workers yet. So the compressor falls back to a simpler path: using a regular <canvas> on the main thread.

It’s slower, yes - but it still works fully offline. And because it’s all written in plain JavaScript, there’s no WASM build or third-party code to load.

This graceful fallback is what makes browser-native tools durable. They degrade gently, not catastrophically.


Keeping Memory in Check

Every Object URL, every bitmap, every Blob gets cleaned up after use. The app explicitly calls bitmap.close() when possible and revokes preview URLs once you delete or clear files.

These small details matter. Client-side tools run on real devices - laptops, tablets, phones - with real memory limits. Efficiency is a kind of respect.


The Interface: Honest and Responsive

The front-end reflects the same design principles: minimal state, clear feedback.

Each file card shows:

  • the filename and preview,
  • the original and compressed sizes,
  • the compression percentage,
  • and its current state - ready, compressing, done, or error.

You can compress images individually or all at once. You can clear the list, re-run compression, or download a single file. Nothing hides behind a spinner - you see exactly what’s happening.

The progress bar at the bottom isn’t just decoration; it’s derived from actual completion state. When workers finish tasks, they trigger updates, and the progress fills accordingly. It feels alive without being noisy.


One Click, One ZIP

When all images are done, you can click Download all as ZIP.

That triggers another client-side workflow - powered by [JSZip](https://github.com/Stuk/jszip) - that bundles every blob into an archive in memory and triggers a download through an invisible <a> element.

There’s no upload step, no waiting for a server to build the ZIP. Your browser literally builds the archive itself.

It’s a small piece of magic that makes the web feel local again.


Real-World Tips

The best part about local compression is that it encourages experimentation. You can see the tradeoffs instantly - smaller sizes, lower quality, faster load.

A few simple habits make the most of it:

  • Start around 80% quality. That’s the sweet spot for most JPEGs and WebPs.
  • Use a max width. 1600–2000px is enough for nearly all web layouts.
  • Don’t double-compress. If you’ve already saved a file at 80%, re-encoding it won’t shrink it much more.
  • Use PNG sparingly. It’s lossless and heavy. WebP often cuts file size by half with almost identical results.

These aren’t just technical choices - they’re editorial ones. Every kilobyte you save makes your site feel faster and more considerate.


Why This Matters for Creators

A lightweight image isn’t just easier on bandwidth. It feels faster - and that feeling affects everything: engagement, bounce rates, perception of quality.

But beyond performance, there’s something deeper happening here: tools like this return agency to the user.

You no longer depend on external services to prepare your content. You don’t trade privacy for convenience. You just open a browser tab and get to work.

That’s how the web was meant to feel.


Beyond Compression

Once you understand this pattern - decode, transform, encode - the possibilities multiply.

You can clean EXIF data, generate thumbnails, build sprite sheets, or even prepare assets for print - all locally. The browser becomes a quiet production studio.

It’s the same foundation behind many Vayce tools: the Image Converter and Progressive JPEG Converter all follow this idea. They just use different ingredients.

Read More From Our Blog

Explore Our Tools