JPG Image Compressor
Reduce JPEG file size instantly in your browser — your files never leave your device.
Drop images here or click to select
JPG, PNG, WebP, GIF, BMP, AVIF, TIFF — multiple files supported
How to compress JPG images
- Drop your images onto the compressor above — or click to browse. Any format works: JPG, PNG, WebP, GIF, BMP, AVIF, TIFF.
- Adjust the quality slider to control the compression level. Lower quality means smaller files.
- Click Compress on a single file or Compress all to process everything at once.
- Download files individually or click Download all to get a ZIP archive with all compressed JPGs.
Your photos stay on your device — 100% private
Most online image compressors upload your file to a server, compress it there, and hand back a download link. Your photo sits on infrastructure you do not control, for a retention period you were never told. This tool does none of that: compression runs inside your browser tab, using the same Canvas API that every browser already ships.
That has one consequence worth stating plainly — there is no upload progress bar because there is no upload. Open DevTools, switch to the Network panel, and compress a file: you will see zero outgoing requests carrying image data. Once the page has loaded you can even disconnect from the network and the compressor keeps working.
// This is the entire pipeline — it never touches a server
const img = new Image()
img.src = URL.createObjectURL(file) // read the local file
// …
canvas.getContext('2d').drawImage(img, 0, 0)
canvas.toBlob(blob => download(blob), 'image/jpeg', 0.82)For client work this matters. Product shots under embargo, internal screenshots, photos of documents — none of it should be passing through a stranger's server just to lose a few hundred kilobytes.
What is JPG compression and when should you use it?
JPG (or JPEG) uses lossy compression — it discards image data that the human eye is least likely to notice in order to achieve smaller files. The quality slider controls how aggressively that happens: higher quality keeps more data and produces larger files, lower quality throws more away and produces smaller files with visible artifacts.
Technically, the encoder converts the image to a colour space that separates brightness from colour, subsamples the colour channels (your eye is far more sensitive to brightness than to hue), splits the image into 8×8 pixel blocks, and rounds off the fine detail in each block. The quality value decides how much rounding happens. That is why JPG artifacts look like blocky squares and haloes around sharp edges — you are seeing those 8×8 blocks.
When JPG compression saves you the most
- Photographs and complex images with many colours and soft gradients — this is exactly what JPG was designed for
- Web pages that need to load fast — images are usually the heaviest thing on a page, so this is the quickest Core Web Vitals win available
- Email attachments — most corporate mail servers reject anything over 10–25 MB
- Social media uploads — platforms re-compress whatever you upload, so uploading a pre-compressed file gives you control over how it looks instead of leaving it to their encoder
When JPG is the wrong tool
- Anything with transparency — JPG has no alpha channel, transparent pixels become solid white
- Logos, icons, screenshots, text — sharp edges get haloes; use PNG or WebP instead
- Master files you will keep editing — every save is another generation of loss
JPG quality settings — what each level actually costs
Percentages in the slider are not a linear scale of "how good it looks". Below is what each band realistically produces for a typical 12-megapixel photograph.
| Quality | Typical size cut | What you see | Use it for |
|---|---|---|---|
| 95–100% | 10–25% | Visually identical to the original; files stay large | Archival copies, print masters |
| 85–90% | 40–60% | No visible difference at 100% zoom on a photo | Portfolio images, product photography, hero images |
| 75–85% | 50–70% | Artifacts visible only when pixel-peeping | The default for almost all web use |
| 60–75% | 70–80% | Soft haloes around edges, slight gradient banding | Blog body images, backgrounds, gallery grids |
| 40–60% | 80–90% | Clearly visible blocking in flat areas | Thumbnails, previews, placeholders |
| Below 40% | 90%+ | Obvious 8×8 blocks, colour smearing | Only when size matters more than looks |
If you are unsure, start at 80%, compress, and click the preview thumbnail to inspect the result full-screen before you download. Adjust and re-compress — nothing is committed until you download.
Generation loss — why you should always compress from the original
JPG compression is not idempotent. Compressing an already-compressed JPG does not simply "keep the current quality" — the encoder starts from the artifacts left by the previous pass and adds its own on top. Do this a few times and flat areas turn muddy, edges pick up haloes, and colours drift. This is called generation loss, and it cannot be undone.
- Always compress from the original camera file or export, not from a copy someone sent you over a messenger
- Keep the original somewhere — this tool never modifies your source file, but it also cannot recover one you have overwritten
- If you need to edit later, edit the original and compress at the end, not the other way round
- Screenshots pasted from a chat app have usually been compressed at least once already
How browser-based JPG compression works under the hood
Every step below happens inside the browser tab, in memory. No file is written to disk until you click download, and nothing is sent anywhere.
// 1. Read the file the user dropped — a local Blob URL, no network
const img = new Image()
const url = URL.createObjectURL(file)
img.onload = () => {
// 2. Draw it onto an off-screen canvas at its native resolution
const canvas = document.createElement('canvas')
canvas.width = img.naturalWidth
canvas.height = img.naturalHeight
canvas.getContext('2d').drawImage(img, 0, 0)
// 3. Re-encode. The browser's own JPEG encoder does the work.
// quality is 0–1 — the slider value divided by 100.
canvas.toBlob(
(blob) => {
URL.revokeObjectURL(url) // 4. free the memory immediately
resolve(blob) // 5. hand back an in-memory Blob
},
'image/jpeg',
0.82,
)
}
img.src = urlTwo side effects are worth knowing about. First, drawing to a canvas strips all metadata — EXIF, GPS coordinates, camera model, embedded colour profile and thumbnails are gone from the output. That is usually a privacy bonus, but if you need to keep copyright or orientation tags, note that they will not survive. Second, because the source is decoded to raw pixels first, any input format works — drop a PNG, WebP, AVIF, TIFF or BMP and you get a compressed JPG out of it.
Batch downloads are assembled the same way: each compressed Blob is added to a ZIP archive built in memory by JSZip, then handed to the browser as a single download.