DevToys Web Pro iconDevToys Web ProBlog
Được dịch bằng LocalePack logoLocalePack
Đánh giá chúng tôi:
Dùng thử tiện ích mở rộng trình duyệt:
← Back to Blog

Image Compressor Guide: JPEG, PNG, WebP, and Core Web Vitals

13 min read

Images are the single largest contributor to page weight on most websites. A hero image served at the wrong quality setting or in the wrong format can add 500 KB to a page that should load in under a second on mobile. The difference between a Largest Contentful Paint (LCP) of 1.8 s and 4.5 s is often a single unoptimized image. Use the Image Compressor to compress images directly in your browser — no upload, no server, nothing leaving your machine.

This guide explains exactly how compression works at the algorithm level for JPEG, PNG, WebP, and AVIF, gives you concrete quality thresholds and file-size targets, and shows working code for automating compression in Node.js pipelines.

Lossy vs Lossless Compression

The most important distinction in image compression is whether the original pixel data can be recovered exactly after decompression.

Lossless compression (PNG, WebP lossless, GIF) removes redundancy in the data — repeated patterns, runs of identical bytes — without discarding any information. You can decompress back to bit-for-bit identical pixels. The tradeoff is ceiling: lossless compression of photographic content rarely achieves more than 20–40% reduction because photos have very little pixel-level redundancy.

Lossy compression (JPEG, WebP lossy, AVIF) discards information the human visual system is least sensitive to: high-frequency detail in smooth gradients, color detail at fine spatial scales, subtle texture in out-of-focus areas. Once discarded, that information is gone. Re-encoding a JPEG as a JPEG introduces a new round of loss on top of the first — a phenomenon called generation loss. For photographic content, lossy formats routinely achieve 70–90% reduction at visually acceptable quality.

PropertyLosslessLossy
Pixel fidelityExactApproximated
Compression ratio (photos)20–40%70–95%
Re-encoding penaltyNoneCumulative quality loss
Best content typeScreenshots, logos, line artPhotographs, illustrations with gradients
FormatsPNG, WebP lossless, GIFJPEG, WebP lossy, AVIF

The practical rule: use lossy formats for photos and complex illustrations; use lossless formats for UI elements, icons, screenshots, and anything with hard edges or text.

How JPEG Compression Works

JPEG uses a pipeline of three steps: color space conversion, frequency decomposition via the Discrete Cosine Transform (DCT), and quantization.

1. Color space conversion. The image is converted from RGB to YCbCr, which separates luma (brightness, Y) from chroma (color, Cb and Cr). Human vision is far more sensitive to luma detail than chroma detail, so JPEG exploits this by storing chroma channels at half resolution (4:2:0 chroma subsampling by default). This alone accounts for a significant portion of JPEG's efficiency on photos.

2. DCT. The image is split into 8×8 pixel blocks. Each block is transformed from the spatial domain (pixel values) into the frequency domain (how rapidly pixel values change across the block). The result is 64 coefficients per block: one DC coefficient representing the average color, and 63 AC coefficients representing increasingly fine detail.

3. Quantization. Each of the 64 DCT coefficients is divided by a value from a quantization table and rounded to the nearest integer. High-frequency coefficients (fine detail) get large divisors — their values round to zero, which means that information is discarded. The quality setting controls how aggressive these divisors are. This is where lossy compression happens.

A critical misunderstanding about JPEG quality settings: quality is not linear. The difference between q=90 and q=85 is perceptually almost invisible and saves maybe 15% file size. The difference between q=80 and q=60 is noticeable in some content but saves 30–50%. Below q=50, block artifacts (the characteristic 8×8 grid pattern) become obvious. The useful working range for web images is q=60 to q=85.

Quality RangeTypical File Size vs q=100Artifact LevelUse Case
q=90–10060–100% (near-original)None visiblePrint, source archiving
q=80–8930–60%None visibleHero images, product photos
q=70–7920–35%Barely perceptibleEditorial images, blog content
q=60–6915–25%Visible on close inspectionThumbnails, card images
q=40–598–18%Clearly visibleAggressive thumbnails only
q=1–39<10%Severe blockingAvoid for public-facing images

Progressive JPEG encodes the image in multiple passes: a low-resolution version first, then progressively finer detail. For images above ~10 KB, progressive encoding often produces slightly smaller files than baseline JPEG and gives a better perceived loading experience in browsers.

How PNG Compression Works

PNG is lossless. Its compression pipeline is two stages: filter prediction and DEFLATE.

Filter prediction. Before compression, PNG applies a filter to each row of pixels. The default adaptive filter picks from five options per row (None, Sub, Up, Average, Paeth) and chooses whichever makes the row most compressible. The Paeth filter, for example, predicts each byte from a combination of the pixel to the left, above, and diagonally above-left. Subtracting the prediction from the actual value gives a residual that trends toward zero — zeros compress extremely well.

DEFLATE. The filtered data is then compressed with DEFLATE (the same algorithm as gzip), which combines LZ77 (finding repeated byte sequences) with Huffman coding (assigning shorter codes to more frequent symbols).

Why PNG does not compress photographs well: photographic images have almost no repeated pixel sequences. Every pixel is slightly different from its neighbors. Filter prediction helps but produces non-zero residuals throughout. DEFLATE has nothing to grab onto. A typical 1200×800 photograph as PNG is 1.5–4 MB; as JPEG at q=80 it is 80–200 KB — a 10x+ difference.

PNG's strengths are hard edges, flat colors, and transparency. Screenshots, UI mockups, logos, and icons have large areas of identical pixels and sharp boundaries that compress extremely well. A 1200×800 screenshot with a mostly white background might compress to under 100 KB as PNG.

PNG compression level (0–9) controls the DEFLATE effort, not the visual quality (which never changes — PNG is always lossless). Level 9 tries harder and produces slightly smaller files at the cost of more CPU time. Level 6 is the standard default and gets within a few percent of level 9.

How WebP and AVIF Compression Differ

Both WebP and AVIF support lossy and lossless modes and generally outperform JPEG and PNG at equivalent visual quality. Their internal mechanics differ significantly.

WebP (lossy) is based on the VP8 video codec's intra-frame encoding. It uses block-based prediction but with larger and more flexible block sizes than JPEG's fixed 8×8 grid. The prediction step estimates each block's content from already-encoded neighboring blocks, and only the residual (difference from prediction) is encoded. This prediction step is what makes WebP consistently 25–35% smaller than JPEG at equivalent SSIM (structural similarity) quality.

WebP (lossless) uses a completely different algorithm: spatial prediction, color space transforms, a palette-based approach for images with few colors, and LZ77+Huffman entropy coding. For PNG-type content (screenshots, UI), WebP lossless is typically 20–30% smaller than PNG.

AVIF is based on the AV1 video codec. Its compression is more sophisticated than WebP: larger transform sizes (up to 64×64), more prediction modes, film-grain synthesis (for natural-looking texture at high compression), and better high-frequency detail preservation. AVIF typically beats WebP by another 20–30% at equivalent visual quality. The tradeoff is encoding speed: AVIF encoding is significantly slower than WebP, which matters for on-the-fly resizing pipelines.

Formatvs JPEG (same quality)Encode SpeedBrowser SupportBest For
JPEGBaselineVery fastUniversalMaximum compatibility
WebP25–35% smallerFast97%+Default modern choice
AVIF40–55% smallerSlow90%+Maximum compression, static assets
PNGLossless onlyFastUniversalScreenshots, logos, icons

For format conversion between these types, use the Image Converter. For AVIF and HEIF specifics, see the AVIF and HEIF Image Formats guide.

Practical Quality Thresholds

These are production-tested thresholds used by major web platforms. They represent the point where further quality reduction produces user-noticeable degradation that outweighs the bandwidth savings.

JPEG

  • Hero images and full-width banners: q=80–85. At 1200–2400px wide, q=80 produces files of 80–200 KB that look pristine on retina displays.
  • Product photos (e-commerce): q=75–80. Users zoom in, so keep quality higher than editorial images.
  • Blog and editorial images: q=70–75. Viewed at fixed size, not zoomed.
  • Thumbnails (150–300px): q=60–70. At small sizes, the eye cannot resolve the artifacts that q=60 introduces.
  • Avatar and icon JPEG fallbacks: q=65–70.

WebP (lossy)

  • Hero images: q=75–80. Equivalent visual quality to JPEG q=85, at 25–35% smaller file size.
  • Thumbnails: q=60–65.
  • Background images (low detail): q=50–60. Low-detail backgrounds tolerate significant compression without visible artifacts.

AVIF

  • Hero images: q=60–70 (AVIF quality scales differently — lower numbers are more aggressive). This is perceptually equivalent to JPEG q=85.
  • Thumbnails: q=50–60.

PNG (lossless)

PNG has no quality setting — it is always lossless. For photographic content that must be PNG (e.g., transparency required), consider quantizing to a limited palette first using a tool like pngquant, which converts 24-bit PNG to 8-bit indexed PNG. This produces lossy output but with far smaller file sizes (often 60–80% reduction) while looking excellent for the right content types.

File Size Targets Per Use Case

Quality settings alone are not enough: you also need to constrain the output dimensions. A 4000×3000px photo at q=80 will still be 1.5 MB. Resize first, then compress.

Use CaseDimensionsTarget SizeFormat
Hero / full-width banner1200–2400px wide<200 KBWebP or AVIF (JPEG fallback)
Product photo800–1200px<150 KBWebP or AVIF
Blog / editorial image700–900px wide<100 KBWebP or JPEG
Card thumbnail300–500px<30 KBWebP or JPEG
Avatar64–128px<10 KBWebP or JPEG
Icon (raster)32–64px<5 KBPNG (or SVG preferred)
Open Graph / social preview1200×630px<150 KBJPEG or WebP

For resizing images before compression, use the Image Resizer. See also the Image Optimization Workflow guide for a complete end-to-end pipeline, and the Image Converter Guide for format selection strategy.

One additional constraint: do not serve an image larger than the largest srcset size you actually render. A 2400px image served to a 375px viewport wastes exactly (2400/375)^2 = 41x the bytes. Use srcset and sizes to serve appropriately sized images per device.

Core Web Vitals Impact

Image size is the primary driver of Largest Contentful Paint (LCP), which is Google's primary page experience signal. Understanding the thresholds and mechanics helps prioritize compression work.

LCP Thresholds

LCP TimeRatingSearch Ranking Impact
<2.5 sGoodPositive signal
2.5–4.0 sNeeds improvementNeutral
>4.0 sPoorNegative signal

The LCP element is almost always an image — a hero banner, product photo, or above-the-fold illustration. On a 4G mobile connection (typical: ~10 Mbps with latency), downloading a 500 KB image takes roughly 400 ms, not counting DNS, TLS, TTFB, and render time. Reducing that image to 100 KB saves 320 ms — more than enough to move from "needs improvement" to "good" on many pages.

Priority Hints

Browsers do not automatically know which image is the LCP element. Use the fetchpriority attribute to promote the LCP image:

<!-- Hero image: high priority, no lazy load -->
<img
  src="/hero.webp"
  srcset="/hero-800.webp 800w, /hero-1600.webp 1600w"
  sizes="100vw"
  alt="Product hero"
  fetchpriority="high"
  loading="eager"
  decoding="async"
  width="1600"
  height="900"
/>

<!-- Below-fold images: defer loading -->
<img
  src="/thumbnail.webp"
  alt="Product thumbnail"
  loading="lazy"
  decoding="async"
  width="400"
  height="300"
/>

Common LCP mistakes: setting loading="lazy" on the hero image (delays discovery until layout completes), serving a 2x image without srcset to all viewports, and missing width/height attributes (causes layout shift before the image loads, hurting CLS).

Image Formats and LCP

Switching from JPEG to WebP for the LCP image is often the highest-ROI change: 25–35% smaller, same quality, 97% browser support. Use a <picture> element for format negotiation:

<picture>
  <source srcset="/hero.avif" type="image/avif" />
  <source srcset="/hero.webp" type="image/webp" />
  <img src="/hero.jpg" alt="Hero" fetchpriority="high" width="1600" height="900" />
</picture>

Compression Workflows in Node.js

For build pipelines, CI, or server-side image processing, you have three main options: sharp, imagemin, and squoosh-cli.

sharp

sharp is the fastest Node.js image processing library, built on libvips. It handles JPEG, PNG, WebP, AVIF, TIFF, and GIF. Use it for resize-and-compress pipelines:

npm install sharp
import sharp from 'sharp';

// Hero image: resize to 1600px wide, convert to WebP at q=80
await sharp('input.jpg')
  .resize({ width: 1600, withoutEnlargement: true })
  .webp({ quality: 80, effort: 4 })  // effort: 0 (fast) to 6 (slow, smaller)
  .toFile('hero-1600.webp');

// Thumbnail: resize to 400px, convert to WebP at q=65
await sharp('input.jpg')
  .resize({ width: 400, withoutEnlargement: true })
  .webp({ quality: 65 })
  .toFile('thumb-400.webp');

// AVIF for maximum compression
await sharp('input.jpg')
  .resize({ width: 1600 })
  .avif({ quality: 65, effort: 6 })  // effort: 0–9, higher = slower + smaller
  .toFile('hero-1600.avif');

// JPEG fallback at q=82
await sharp('input.jpg')
  .resize({ width: 1600 })
  .jpeg({ quality: 82, progressive: true, mozjpeg: true })
  .toFile('hero-1600.jpg');

// Batch processing
import { readdir } from 'fs/promises';
import path from 'path';

const files = await readdir('./images');
const jpgs = files.filter(f => f.endsWith('.jpg'));

await Promise.all(
  jpgs.map(file =>
    sharp(path.join('./images', file))
      .resize({ width: 1200, withoutEnlargement: true })
      .webp({ quality: 80 })
      .toFile(path.join('./dist', file.replace('.jpg', '.webp')))
  )
);

imagemin

imagemin is a plugin-based compressor. It is useful when you have mixed input formats and want format-specific plugins to handle each:

npm install imagemin imagemin-mozjpeg imagemin-pngquant imagemin-webp
import imagemin from 'imagemin';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminPngquant from 'imagemin-pngquant';
import imageminWebp from 'imagemin-webp';

// Compress JPEGs and PNGs in place
const files = await imagemin(['images/*.{jpg,png}'], {
  destination: 'dist/images',
  plugins: [
    imageminMozjpeg({ quality: 80 }),
    imageminPngquant({ quality: [0.65, 0.80] }),  // min/max quality range
  ],
});

// Also generate WebP versions
await imagemin(['images/*.{jpg,png}'], {
  destination: 'dist/images',
  plugins: [
    imageminWebp({ quality: 75 }),
  ],
});

squoosh-cli

@squoosh/cli wraps the same codecs as the Squoosh web app. Useful for one-off compression or when you want to benchmark codecs against each other:

npx @squoosh/cli --webp '{"quality":80}' --avif '{"quality":65}' --suffix '' images/*.jpg

For a complete pipeline combining resizing, format conversion, and srcset generation, see the Image Optimization Workflow guide and the Image Resizer Guide.

Common Pitfalls

Double-Compression (Re-encoding JPEGs)

The most destructive mistake in image pipelines is re-encoding an already-compressed JPEG. Each JPEG encode applies a new quantization step on top of the previous one. The artifacts accumulate: block edges become more pronounced, gradients develop banding, and fine texture becomes mushy. Even re-encoding at q=95 introduces additional loss.

The fix: always keep original, uncompressed (or losslessly compressed) source files. Never use a JPEG as the input to another JPEG compression step. In your build pipeline, source images should be PNG, TIFF, or camera RAW — and the JPEG/WebP/AVIF outputs should be generated fresh from the lossless source each time.

// Wrong: re-encoding an existing JPEG
await sharp('already-compressed.jpg')
  .jpeg({ quality: 80 })
  .toFile('output.jpg');  // double compression damage

// Right: start from the lossless source
await sharp('source.png')  // or .tiff, or RAW
  .jpeg({ quality: 80 })
  .toFile('output.jpg');

Using PNG for Photographs

Serving a photographic PNG instead of a JPEG or WebP is the fastest way to bloat page weight. A 1200×800 photo as PNG: ~2 MB. The same photo as WebP at q=80: ~90 KB. That is a 22x difference. If you are receiving PNG uploads from users and serving them back as PNG, convert to WebP at the point of upload or at serve time.

CSS Filters and Repaints

Applying CSS filters (filter: blur(), filter: brightness(), etc.) to images does not reduce file size — the full image is still downloaded. Worse, some CSS filter combinations prevent the browser from compositing the image on the GPU, causing it to fall back to CPU-based painting. This triggers repaints on scroll or animation that hurt both performance and battery life. Apply visual effects in your image pipeline (sharp supports blur, tint, etc.) and serve the pre-processed image, rather than applying filters at render time.

Ignoring Animated Images

Animated GIFs are notoriously large. A 5-second animation at 640×360 can easily be 10 MB as GIF. The same animation as WebP (animated) or short MP4 can be under 500 KB — a 20x reduction. If you are serving animated content, prefer animated WebP or convert to video with a poster image:

<!-- Prefer: video element for animations -->
<video autoplay loop muted playsinline>
  <source src="animation.webm" type="video/webm" />
  <source src="animation.mp4" type="video/mp4" />
</video>

<!-- Or: animated WebP -->
<img src="animation.webp" alt="Animation" />

Compress images directly in your browser with the Image Compressor — supports JPEG, PNG, WebP, and AVIF, with adjustable quality sliders and side-by-side comparison. For converting between formats, use the Image Converter.