画像圧縮ディープガイド(2026):ブラウザでの可逆・不可逆圧縮の原理と実践
画像圧縮が重要な理由
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 つの落とし穴
- 極めて遅い:2–5 秒/枚(WebP 0.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#前端优化#无损压缩