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 URLImage.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);很多教程直接设 canvas.width = 800; canvas.height = 600;,结果人像被压扁。正确做法是保持原始宽高比,按最大边限制缩放(例如“最长边 ≤ 1200px”)。
性能影响:不缩放只改质量,对 4000×3000 图压缩后仍可能有 2MB;缩放到 1200px 最长边后,同样质量下通常压到 300KB 以内。
实操建议:
scale = Math.min(maxWidth / img.width, maxHeight / img.height),其中 maxWidth 和 maxHeight 取相同值(如 1200)即实现等比约束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 或交换宽高)image/jpeg 格式 + 质量 0.9 以上可缓解偏色;更稳方案是上传前用 createImageBitmap()(支持 orientation 自动修正,但兼容性限于较新版本)