前端压缩解决上传带宽与用户等待问题,后端压缩解决存储体积与访问性能问题;前者用createimagebitmap+canvas.toblob压至800kb左右再上传,后者用spatie等工具清理exif、转webp并异步优化。

要压缩,但必须分清「前端压缩」和「后端压缩」的适用场景——前端压缩解决的是上传带宽与用户等待问题,后端压缩解决的是存储体积与访问性能问题,两者不互斥,也不可互相替代。
前端压缩:用 canvas 或 createImageBitmap 控制上传体积
手机直出图片动辄 5–15MB,直接上传不仅慢,还容易触发 Nginx 的 client_max_body_size 限制或浏览器超时。前端压缩不是为了“替代后端处理”,而是把 12MB 的 JPEG 压到 800KB 左右再发出去,显著降低首包时间与失败率。
- 优先用
createImageBitmap(现代浏览器)+canvas.toBlob,比老式drawImage更准、支持 WebP 和颜色空间控制 - 别依赖
file.size判断是否压缩——iOS Safari 可能返回错误尺寸,应以实际解码后的像素尺寸为准 - 压缩质量设为
0.8而非0.6:前者视觉损失极小,后者在文字截图或 UI 界面图中易出现色块 - 对 SVG、GIF 不做 canvas 压缩——它们不支持
toBlob('image/jpeg'),需单独判断file.type后跳过
后端压缩:用 Spatie\LaravelImageOptimizer 替代手动 resize
前端传来的“已压缩”图片,仍可能含冗余 EXIF、未启用渐进加载、未转 WebP。Laravel 默认的 move() 只是搬运,不做任何优化。这时候靠 Spatie\LaravelImageOptimizer 比用 Intervention Image 手动 resize 更可靠。
- 安装后发布配置:
php artisan vendor:publish --provider="Spatie\LaravelImageOptimizer\ImageOptimizerServiceProvider" - 关键配置项:
'max_size' => 1024 * 1024 * 2(2MB 上限),'optimizers'中为 JPEG 加上--all-progressive,提升弱网加载体验 - 不要在控制器里直接调
ImageOptimizer::optimize($path)——应放在队列任务中异步执行,避免阻塞请求 - 如果用了 Laravel 12 的
InteractsWithMedia,直接在registerMediaCollections()里加->optimized()即可自动触发
绕不开的坑:OSS 直传时前端压缩失效?
用阿里云 OSS 直传时,常见错误是以为“签名后前端上传就完事了”,结果发现上传的仍是原图——因为 OSS SDK 默认不处理 Blob,它只负责把文件二进制原样发过去。你得在调 new OSS.Wrapper(...) 之前,先把 File 对象替换成压缩后的 Blob。
- 别在
uploader.on('beforeupload')里压缩:WebUploader 的 file 对象不可替换,此时改file.source无效 - 正确做法:监听
'select'事件,拿到原始File,用compressImage(file)返回 Promise,再用新 Blob 构造 new File([blob], file.name, {type: 'image/jpeg'})替换原对象 - OSS 直传签名接口返回的
policy是按原始文件大小校验的,所以压缩必须在签名前完成,否则会报InvalidPolicy
真正容易被忽略的点是:前端压缩无法替代后端的格式转换与元数据清理,而后者直接影响 CDN 缓存命中率和 LCP 指标。一张前端压到 500KB 的 JPEG,后端再转成 WebP + 渐进加载,实际页面加载速度可能快 40% 以上——这个差值,只看上传体积是看不出来的。











