画像圧縮ディープガイド(2026):ブラウザでの可逆・不可逆圧縮の原理と実践

工具教程(更新: 2026年7月20日)

画像圧縮が重要な理由

HTTP Archive 2026:画像はページ総バイトの 45%。4000×3000→5 MB 超。WebP (quality=0.8) で 200–400 KB、視覚的に同一。

本記事はピクセルレベル原理、フォーマット選択、ブラウザ API、マルチスレッド、CI/CD まで深掘り。


可逆 vs 不可逆

可逆

  • PNG:DEFLATE+行フィルタ。均一色に強い、写真に弱い。
  • WebP 可逆:PNG より 26% 小。
  • AVIF 可逆:AV1 フレーム内符号化、WebP 可逆より 22% 良好。

不可逆

  • クロマサブサンプリング (4:2:0):色解像度半減、33% 削減。
  • 量子化:DCT 係数を bin に統合。quality = 量子化ステップサイズ。
  • 予測符号化:AVIF 67 予測モード vs WebP 4。

フォーマット選択

フォーマット 可逆/不可逆 透過 アニメ 対応 vs JPEG
JPEG 不可逆 99.9% 基準
PNG 可逆 99.9% 写真最弱
WebP 両方 97% 25–35% 小
AVIF 両方 93% 50% 小

判定:写真→WebP/AVIF 不可逆 0.75–0.85。アイコン→PNG/SVG。透過+写真→WebP/AVIF。アニメ→WebP/AVIF(GIF 比 80%+ 小)。


Canvas + toBlob

async function compressImage(file: File, opts={}): Promise<Blob> {
  const {maxWidth=1920, quality=0.8, format="image/webp"} = opts;
  const img = await createImageBitmap(file);
  let {width, height} = img;
  if (width>maxWidth) { height=Math.round(height*maxWidth/width); width=maxWidth; }
  const c = document.createElement("canvas");
  c.width=width; c.height=height;
  const ctx = c.getContext("2d")!;
  if (format==="image/jpeg") { ctx.fillStyle="#FFF"; ctx.fillRect(0,0,width,height); }
  ctx.drawImage(img,0,0,width,height);
  return new Promise((res,rej)=>c.toBlob(b=>b?res(b):rej(),format,quality));
}

先にリサイズ

4000→750 (375px スマホ 2x retina) = 95%+ 直接削減

品質目安

  • WebP: 0.7–0.85
  • AVIF: 0.4–0.6 (非線形 0–63)
  • JPEG: 0.75–0.9

Web Worker

// compress.worker.ts
self.onmessage = async (e) => {
  const blob = await compressImage(e.data.file, e.data.options);
  const buf = await blob.arrayBuffer();
  self.postMessage({buf, type:blob.type}, [buf]);
};

アップロード前圧縮

async function handleUpload(files: FileList) {
  const pool = Array.from({length: navigator.hardwareConcurrency-1||1},
    () => new Worker(new URL("./compress.worker.ts", import.meta.url)));
  
  const results = await Promise.all(Array.from(files).map(async (file, idx) => {
    if (file.size<50*1024 && file.type==="image/png") return file;
    const compressed = await new Promise<Blob>(r => {
      pool[idx%pool.length].onmessage = e => r(new Blob([e.data.buf], {type:e.data.type}));
      pool[idx%pool.length].postMessage({file, options:{maxWidth:1920,quality:0.78,format:"image/webp"}});
    });
    return new File([compressed], file.name.replace(/\.[^.]+$/,".webp"), {type:"image/webp"});
  }));
  await uploadToServer(results); pool.forEach(w=>w.terminate());
}

CI/CD (Sharp)

const sharp = require("sharp");
await sharp(f).resize(1920).webp({quality:80,effort:4}).toFile(out);

AVIF:3 つの落とし穴

  1. 極めて遅い:2–5 秒/枚(WebP 0.3 秒)
  2. プログレッシブ非対応:読み込み完了まで空白
  3. 93% 対応<picture> フォールバック必須
<picture>
  <source srcset="photo.avif" type="image/avif">
  <source srcset="photo.webp" type="image/webp">
  <img src="photo.jpg" alt="" loading="lazy">
</picture>

高度テクニック

段階的品質:メイン=0.85/1600、サムネイル=0.7/400、背景=0.6/40+blur。LQIP:10×10 プレースホルダ→フル。pHash 重複除去:ハミング距離<10→再利用。


FAQ

Q1:toBlob が null?——Canvas 0×0 または CORS 未設定。img.crossOrigin="anonymous"

Q2:WebP/AVIF quality 同じ?——異なる。WebP 80≈AVIF 50 (SSIM)。AVIF = WebP の 60–70%。

Q3:PNG 写真が大きくなる?——可逆は高周波に弱い。写真は常に不可逆。

Q4:toBlob vs toDataURL?——常に toBlob。toDataURL は Base64 +33% + 一括メモリ。

Q5:プライバシー?——全ローカル処理。データはブラウザを出ない。


結論

戦略:リサイズ → フォーマット → クライアント事前圧縮 → サーバーフォールバック → 段階的品質

#图片压缩#WebP#AVIF#Canvas#前端优化#无损压缩