一.需求描述
将不同体积的图片压缩至目标大小,比如5M。
二.需求分析
1.较新的 iPhone 默认使用 HEIC 格式,而大多数 Android 设备仍然使用 JPEG 格式
2.压缩考虑从两个角度,一个是像素点的数量(分辨率),一个是像素点的质量。可根据实际需求进行取舍。
比如压缩后的图片是用于导出到pdf,pdf文件内嵌入的图片,照片尺寸较小,且用户没有点击图片看大图的操作可能性(如果是word,那用户可以调整图片的大小),所以压缩后图片尺寸可以比较小。相对于主流手机2k 4k的照片质量,在像素密度方面有比较大的操作空间。
比如压缩后的照片没有特定使用场景,那就不能把像素压得太低,因为用户可能要在pc端大图查看,甚至放大。此时就需要综合像素点和像素质量来调优。
注意点,对于png格式的图片,压缩往往不理想,此时可以先把格式转为jpeg,转格式的过程本身就会减小一部分体积。
三.核心方案
使用Compressor.js
压缩并不是线性的(比如压缩比0.3和0.6得到的体积并不是2倍的关系),quality的最佳取值可能需要通过测试来寻找
四.详细示例
// html
<input type="file" id="upload" accept="image/*">
// 监听上传
document.getElementById('upload').addEventListener('change', (event) => {
const file = event.target.files[0];
handleCompress(file)
});
// 理想的目标体积byte
const limitSize = 200000 // 200k
// 执行一次压缩
function compressFile(file, options) {
return new Promise((resolve, reject) => {
new Compressor(file, {
...options,
success(result) {
resolve({
isSuccess: true,
result
})
},
error(err) {
reject({
isSuccess: false,
err
})
},
});
})
}
// 整个压缩过程,分为3步
async function handleCompress (file) {
// 设置一定的误差空间(20%),如果某次压缩后,仅比目标大一点,若再多压一次,就太小了
if (file.size <= limitSize * 1.2) { // 不需要压缩
return {
isSuccess: true,
result: file,
}
}
const {type} = file;
const isPng = type === 'image/png';
console.log('原图片', file.size / 1000);
let result
let resultImg = file
// 步骤1,如果是png格式,先使用convertTypes参数转一下格式,起到减小体积的效果,同时方便后续继续压缩
if (isPng) {
console.log('png转格式');
try {
result = await compressFile(file, {
quality: 1,
convertSize: limitSize, // 超过convertSize的图片会转换格式以进一步压缩
})
} catch (error) {
result = error
}
if (result.isSuccess) {
resultImg = result.result
} else {
console.log(result.err);
}
}
console.log(isPng ? `png转格式后:${resultImg.size / 1000}` : '不是png格式,不需要转格式');
if (!resultImg) return '压缩失败'
// 步骤2,如果resultImg大于limitSize * 1.2,采用压缩分辨率(不超过2k)的方式压缩
if (resultImg.size > limitSize * 1.2) {
try {
result = await compressFile(resultImg, {
quality: 1, // 不降低像素点质量
maxWidth: 1920 / 2, // 设置最大宽度,宽高根据需求,一般来说pc端可以压到2k,移动端就可以更小
maxHeight: 1080 / 2, // 设置最大高度,插件会根据图片尺寸和设置的宽高进行计算,宽高比保持不变
})
} catch (error) {
result = error
}
if (result.isSuccess) {
resultImg = result.result
} else {
console.log(result.err);
}
}
console.log('采用压缩分辨率后:', resultImg.size / 1000);
if (!resultImg) return '压缩失败'
// 步骤3,如果resultImg大于limitSize * 1.2,采用压缩像素点质量的方式压缩
let compresseQualityCount = 0
// 多次压缩,直到达到理想体积或者无法明显压缩为止
while(resultImg.size > limitSize * 1.2) {
try {
result = await compressFile(resultImg, {
quality: 0.92 // 这个数很难确认,姑且使用默认。因为压缩比和得到的体积不是线性的
})
} catch (error) {
result = error
}
if (result.isSuccess) {
resultImg = result.result
} else {
console.log(result.err);
break
}
if (++compresseQualityCount >= 3) break // 当limitSize比较小时,可能到某次之后已经无法再压了,避免跳不出循环,数字3可根据情况调整
console.log('采用压缩像素点质量后:', resultImg.size / 1000);
}
if (!resultImg) return '压缩失败'
return result
// 下载
const blobUrl = URL.createObjectURL(resultImg);
const a = document.createElement('a');
a.href = blobUrl;
a.download = file.name || 'file';
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(a.href);
document.body.removeChild(a);
}