Forms love to say "upload a photo under 50KB". Phone photos are 3MB. Most "compress image" sites make you upload the file to a server first, which is slow and not great for ID photos.
You can do it entirely in the browser with the canvas API. Here is the approach I used for a small tool I built, and a minimal version you can copy.
The idea
canvas.toBlob(callback, 'image/jpeg', quality) lets you choose a JPEG quality between 0 and 1. File size goes up as quality goes up, so you can binary search the quality that gets closest to your target size without going over.
If even the lowest quality is too big, shrink the pixel dimensions and try again.
Minimal code
js
function toBlob(canvas, type, q) {
return new Promise(res => canvas.toBlob(res, type, q));
}
async function compressTo(img, targetBytes) {
let w = img.naturalWidth, h = img.naturalHeight;
for (let i = 0; i < 18; i++) {
const c = document.createElement('canvas');
c.width = Math.round(w);
c.height = Math.round(h);
const ctx = c.getContext('2d');
ctx.fillStyle = '#fff'; // JPEG has no transparency
ctx.fillRect(0, 0, c.width, c.height);
ctx.drawImage(img, 0, 0, c.width, c.height);
let lo = 0.05, hi = 0.95;
let best = await toBlob(c, 'image/jpeg', lo);
if (best.size <= targetBytes) {
// binary search for the highest quality that still fits
for (let j = 0; j < 7; j++) {
const mid = (lo + hi) / 2;
const b = await toBlob(c, 'image/jpeg', mid);
if (b.size <= targetBytes) { best = b; lo = mid; } else { hi = mid; }
}
return best;
}
// too big even at lowest quality: shrink dimensions by 15% and retry
w *= 0.85; h *= 0.85;
}
return null;
}
Use it like this:
js
const file = input.files[0];
const img = new Image();
img.onload = async () => {
const blob = await compressTo(img, 50 * 1024); // 50KB
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'photo-50kb.jpg';
a.click();
};
img.src = URL.createObjectURL(file);
Details that matter
Fill the canvas white first. PNGs with transparency turn black when exported as JPEG otherwise.
Seven search steps is enough. The quality range is narrowed to under 1 percent, which is well below what you can see.
Resize only when you have to. Lowering quality first keeps the pixel size, which many forms also check.
Nothing leaves the device. The file is read with URL.createObjectURL, so no upload is needed. That is a real privacy win for ID photos and documents.
PNG ignores quality. toBlob only uses the quality argument for JPEG and WebP, so convert to JPEG for size targets.
Try it without writing code
I turned this into a small free tool with presets for the common limits: compress an image to 50KB, or pick any other size from the full list of compressors. It also does exact-pixel resizing and format conversion, all client-side.
Happy to answer questions about the approach in the comments.
Top comments (0)