FileReader 是唯一能将文件转为 data URL 的标准 API,但 readAsDataURL 易致大图 OOM;需在 Image.onload 后绘图,用 toBlob 压缩并控制格式质量,按最长边等比缩放,解析 EXIF 修正方向,iOS 需额外处理偏色。

用 FileReader 读取图片再转成 canvas 压缩
原图直接上传体积大、耗流量,浏览器端压缩得先把它变成可操作的像素数据。FileReader 是唯一能从 input[type="file"] 获取图片二进制并转为 data URL 的标准 API。但注意:readAsDataURL 会把整个文件加载进内存,大图(比如 10MB 手机照片)容易卡顿甚至 OOM。
实操建议:
- 监听
input的change事件,取e.target.files[0],优先校验type是否为image/jpeg或image/png - 用
FileReader.readAsDataURL()读取后,在onload回调里创建Image对象,src设为该 data URL -
Image.onload触发后,才能安全地画到canvas上——过早绘图会导致 canvas 空白 - 压缩逻辑放在
Image.onload内,避免跨域问题(本地文件无跨域,但若后续改用 URL 加载远程图就得加crossOrigin="anonymous")
canvas.toBlob() 控制质量与格式,比 toDataURL() 更省内存
toDataURL() 返回 base64 字符串,体积比二进制大 33%,且无法流式处理;而 toBlob() 直接生成 Blob 对象,可直接传给 FormData 上传,也支持指定质量(仅对 image/jpeg 和 image/webp 有效)。
常见错误现象:设了 quality: 0.7 但 PNG 图没变小——因为 PNG 不支持质量参数,toBlob() 会忽略它,仍输出无损 PNG。
立即学习“前端免费学习笔记(深入)”;
实操建议:
- 统一转成
image/jpeg再压缩,兼容性好、体积小;若需透明通道,才保留 PNG 并改用尺寸缩放降质 - 调用写法:
canvas.toBlob(blob => { const formData = new FormData(); formData.append('file', blob, 'compressed.jpg'); // 后续 fetch 上传 }, 'image/jpeg', 0.8); - 质量值建议 0.6–0.8:低于 0.5 易出现明显色块,高于 0.8 压缩收益极低
按目标尺寸缩放而非固定宽高比,避免拉伸或裁剪
很多教程直接设 canvas.width = 800; canvas.height = 600;,结果人像被压扁。正确做法是保持原始宽高比,按最大边限制缩放(例如“最长边 ≤ 1200px”)。
性能影响:不缩放只改质量,对 4000×3000 图压缩后仍可能有 2MB;缩放到 1200px 最长边后,同样质量下通常压到 300KB 以内。
实操建议:
- 计算缩放比例:
scale = Math.min(maxWidth / img.width, maxHeight / img.height),其中maxWidth和maxHeight取相同值(如 1200)即实现等比约束 - canvas 宽高设为
Math.floor(img.width * scale)和Math.floor(img.height * scale),避免小数导致渲染模糊 - 用
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)拉伸绘制,不加额外坐标参数
移动端真机测试时注意 EXIF 方向和 iOS Safari 的 canvas 渲染 bug
手机拍的照片带 EXIF 旋转信息(如 Orientation: 6 表示顺时针转 90°),但 canvas.drawImage() 默认忽略它,导致上传后图片歪着。iOS Safari 还有个经典 bug:横屏拍摄的 JPEG 在 canvas 上绘制后颜色偏灰、对比度下降。
容易被忽略的地方:
- 必须用
exif-js或piexifjs解析file的 EXIF,根据Orientation值动态调整 canvas 绘制逻辑(比如旋转 canvas context 或交换宽高) - iOS Safari 下,强制用
image/jpeg格式 + 质量 0.9 以上可缓解偏色;更稳方案是上传前用createImageBitmap()(支持 orientation 自动修正,但兼容性限于较新版本) - 真机调试别只信 Chrome DevTools 的 device mode——它不模拟 EXIF 和 GPU 渲染差异











