前言
在现代 Web 应用中,图片上传是一个非常常见的功能。然而,用户上传的原图往往尺寸很大(动辄几MB甚至十几MB),直接上传会带来诸多问题:
- 网络传输慢:大文件上传耗时长,用户体验差
- 服务器压力大:存储和处理大量高清图片消耗资源
- 加载速度慢:展示时需要加载大图,影响页面性能
因此,在前端对图片进行压缩处理成为了一个最佳实践。本文将介绍一个实用的图片压缩方案,并详细解析其实现原理。
核心需求
我们需要实现一个函数,能够:
- 接收图片 Blob 对象作为输入
- 对图片进行等比缩放(不超过指定的最大宽高)
- 压缩图片质量
- 输出 Base64 格式的 Data URL,方便预览或上传
完整实现
// 使用 Canvas 对图片进行压缩与等比缩放
const compressImageBlob = (blob, { maxWidth = 1600, maxHeight = 1600, quality = 0.8, mimeType = 'image/jpeg' } = {}) => {
return new Promise((resolve, reject) => {
try {
const img = new Image()
const url = URL.createObjectURL(blob)
img.onload = () => {
try {
let { width, height } = img
// 计算目标尺寸(等比缩放至不超过最大宽高)
const scaleW = maxWidth > 0 ? maxWidth / width : 1
const scaleH = maxHeight > 0 ? maxHeight / height : 1
const scale = Math.min(1, scaleW, scaleH) // 只缩小,不放大
const targetW = Math.round(width * scale)
const targetH = Math.round(height * scale)
const canvas = document.createElement('canvas')
canvas.width = targetW
canvas.height = targetH
const ctx = canvas.getContext('2d')
// 使用高质量缩放
ctx.imageSmoothingEnabled = true
ctx.imageSmoothingQuality = 'high'
ctx.drawImage(img, 0, 0, targetW, targetH)
const dataUrl = canvas.toDataURL(mimeType, quality)
URL.revokeObjectURL(url)
resolve(dataUrl)
} catch (err) {
URL.revokeObjectURL(url)
reject(err)
}
}
img.onerror = (e) => {
URL.revokeObjectURL(url)
reject(e)
}
img.src = url
} catch (e) {
reject(e)
}
})
}
技术细节解析
1. 等比缩放算法
const scaleW = maxWidth > 0 ? maxWidth / width : 1
const scaleH = maxHeight > 0 ? maxHeight / height : 1
const scale = Math.min(1, scaleW, scaleH)
这段代码巧妙地实现了等比缩放:
- 分别计算宽度和高度的缩放比例
- 取两者中较小的那个,确保图片不会超出任一边界
- 使用
Math.min(1, ...)确保不会放大图片,只会缩小
举例说明:
- 原图 2400×1800,限制 1600×1600
- scaleW = 1600/2400 = 0.667
- scaleH = 1600/1800 = 0.889
- scale = 0.667(取较小值)
- 结果:1600×1200(宽度占满,高度等比缩放)
2. Canvas 高质量渲染
ctx.imageSmoothingEnabled = true
ctx.imageSmoothingQuality = 'high'
这两行代码很重要:
imageSmoothingEnabled:启用图像平滑算法imageSmoothingQuality:设置为 'high' 使用最高质量的插值算法
没有这些设置,缩放后的图片可能会出现锯齿或模糊。
3. 内存管理
const url = URL.createObjectURL(blob)
// ... 使用完毕后
URL.revokeObjectURL(url)
URL.createObjectURL() 会创建一个内存引用,使用完毕后必须调用 revokeObjectURL() 释放,否则会造成内存泄漏。注意代码中所有可能的退出路径都正确释放了资源。
4. 质量控制
canvas.toDataURL(mimeType, quality)
mimeType:输出格式,推荐 'image/jpeg'(更小的体积)quality:0-1之间,0.8 是一个经验值,在质量和体积间取得平衡
使用示例
// 从文件上传获取
const fileInput = document.querySelector('input[type="file"]')
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0]
if (!file) return
try {
// 压缩图片
const base64 = await compressImageBlob(file, {
maxWidth: 1200,
maxHeight: 1200,
quality: 0.75
})
console.log('压缩前:', (file.size / 1024).toFixed(2), 'KB')
console.log('压缩后:', (base64.length * 0.75 / 1024).toFixed(2), 'KB')
// 预览
const img = document.createElement('img')
img.src = base64
document.body.appendChild(img)
// 或者上传到服务器
await uploadToServer(base64)
} catch (err) {
console.error('压缩失败:', err)
}
})
常见问题与优化
Q1: 为什么不直接使用 FileReader?
FileReader 只能读取原始文件内容,无法进行压缩和缩放。Canvas 方案可以完全控制输出尺寸和质量。
Q2: 支持哪些图片格式?
理论上支持浏览器能够解码的所有格式(JPEG、PNG、WebP、GIF 等)。输出格式可以通过 mimeType 参数指定。
Q3: 如何处理 HEIC/HEIF 格式?
iOS 设备拍摄的照片可能是 HEIC 格式,浏览器支持有限。建议:
- 在服务器端转换
- 使用第三方库如
heic2any先转换为 JPEG
Q4: 能否进一步优化?
可以考虑:
- 使用 WebP 格式:同等质量下体积更小
- 使用 OffscreenCanvas:在 Worker 中处理,不阻塞主线程
- 分块处理:超大图片分块绘制,避免内存溢出
实际效果
以一张 4000×3000 像素、5MB 的照片为例:
| 场景 | 参数设置 | 输出尺寸 | 文件大小 | 压缩率 |
|---|---|---|---|---|
| 原图 | - | 4000×3000 | 5.2MB | - |
| 压缩1 | 1600×1600, 0.8 | 1600×1200 | 280KB | 94.6% |
| 压缩2 | 1200×1200, 0.75 | 1200×900 | 165KB | 96.8% |
| 压缩3 | 800×800, 0.7 | 800×600 | 85KB | 98.4% |
可以看到,合理的压缩设置可以将文件体积减少 95% 以上,同时保持良好的视觉效果。
总结
这个图片压缩方案具有以下优点:
✅ 纯前端实现:无需后端支持,减轻服务器压力
✅ 智能等比缩放:保持图片比例,不会变形
✅ 质量可控:灵活调整压缩参数
✅ 内存安全:正确处理资源释放
✅ 易于集成:Promise 接口,配合 async/await 使用方便
在实际项目中,可以根据业务需求调整 maxWidth、maxHeight 和 quality 参数,在用户体验、传输速度和图片质量之间找到最佳平衡点。
文章评论