纯前端实现图片压缩:OffscreenCanvas、质量权衡与 EXIF 方向修正

举报
yd_213843956 发表于 2026/10/25 07:51:38 2026/10/25
【摘要】 上传前在浏览器里压缩图片,能省带宽也能省等待。这篇讲清楚 OffscreenCanvas 怎么用、质量和尺寸怎么取舍,以及为什么手机照片压缩后会横过来。

图片上传前先在浏览器里压一遍,能明显减少流量和等待时间,尤其是手机拍的照片。实现不复杂,但有几个坑:压缩后的尺寸怎么定、质量参数怎么选、为什么方向会变。这篇文章按顺序讲清楚。

一、把图片画到 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 方向。把这三件事做对,绝大多数上传场景都能在保证观感的前提下把体积降下来一大截。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。