纯前端实现图片压缩:OffscreenCanvas、质量权衡与 EXIF 方向修正
图片上传前先在浏览器里压一遍,能明显减少流量和等待时间,尤其是手机拍的照片。实现不复杂,但有几个坑:压缩后的尺寸怎么定、质量参数怎么选、为什么方向会变。这篇文章按顺序讲清楚。
一、把图片画到 OffscreenCanvas
传统做法是创建 <canvas> 元素再 drawImage。OffscreenCanvas 可以脱离 DOM,在需要批量处理时更合适;即使不放进 Worker,也能减少页面布局开销。
const bitmap = await createImageBitmap(file);
const scale = Math.min(1, maxSide / Math.max(bitmap.width, bitmap.height));
const w = Math.round(bitmap.width * scale);
const h = Math.round(bitmap.height * scale);
const canvas = new OffscreenCanvas(w, h);
const ctx = canvas.getContext('2d');
ctx.drawImage(bitmap, 0, 0, w, h);
const blob = await canvas.convertToBlob({
type: 'image/jpeg',
quality: 0.8,
});
注意 convertToBlob 对 jpeg 才支持 quality,png 会忽略这个参数。想要更小的体积,通常先转成 jpeg,再根据用途决定是否保留透明通道。
二、尺寸和质量怎么取舍
决定体积的主要因素其实是像素数量,不是 quality。把 4000px 的图压到 1600px,再配 0.8 的质量,往往比保持原尺寸、把质量降到 0.5 更清晰、体积也更小。
实践中可以按用途分档:头像 256px、列表缩略图 480px、详情页 1600px 左右。quality 在 0.75~0.85 之间通常已经看不出明显损失;低于 0.6 时,文字和边缘会出现明显块状噪点。
三、为什么手机照片会横过来
手机拍照时,感光元件方向是固定的,照片旋转信息记录在 EXIF 的 Orientation 字段里。很多看图软件会自动按这个字段旋转显示,但 Canvas 不会。直接 drawImage 出来,竖拍的照片就会变成横的。
处理方式有两种:一是读取 EXIF 后按方向手动旋转画布;二是用 createImageBitmap(file, { imageOrientation: 'from-image' }),让浏览器在解码阶段就完成方向修正。后者更省事,现代浏览器支持度也不错。
const bitmap = await createImageBitmap(file, {
imageOrientation: 'from-image',
});
如果必须兼容旧浏览器,就要自己解析 EXIF 的 APP1 段,取到 Orientation 后再对 ctx 做 rotate/scale。这部分代码不难,但容易在镜像方向上写错,建议直接用成熟的小库。
四、先在主线程还是 Worker 里压
单张图片压缩通常几十毫秒到几百毫秒,放主线程能接受,但批量上传时就会卡。把 createImageBitmap、OffscreenCanvas 和 convertToBlob 全部搬进 Worker,主线程只负责收发 Blob,体验会好很多。
// worker.js
self.onmessage = async (e) => {
const { file, maxSide, quality } = e.data;
const bitmap = await createImageBitmap(file, { imageOrientation: 'from-image' });
// ...绘制并 convertToBlob
self.postMessage({ blob });
};
Worker 里没有 URL.createObjectURL 之外的 DOM API,预览图要把 Blob 传回主线程再生成对象 URL,用完记得 revoke,避免内存泄漏。
五、几个容易踩的坑
透明背景变黑。PNG 转 JPEG 时没有 alpha 通道,透明区域会变成黑色。需要先铺一层白底再绘制。
动图变静图。Canvas 只能取第一帧,GIF/WebP 动图压缩后会失去动画,上传前要判断类型。
体积反而变大。已经压过的小图再转一次 JPEG,可能因为重新编码而变大。加一个判断:压缩结果比原文件大就直接用原图。
颜色偏移。带广色域的图片在 Canvas 里可能按 sRGB 解释,颜色会变淡。对颜色要求高的场景,建议保留原图或使用支持色彩管理的处理方式。
六、小结
前端压缩的核心是三件事:按用途限制最大边长、选一个够用的质量档、处理好 EXIF 方向。把这三件事做对,绝大多数上传场景都能在保证观感的前提下把体积降下来一大截。
- 点赞
- 收藏
- 关注作者
评论(0)