1. 场景
在使用Angular+ionic开发混合App的过程中,有一个上传图片的需求。现在手机拍照像素都很高,所以图片都会有点大,随便一张图都7MB左右。而且用户在使用该App大多在工地、隧道等网络不是很好的地方。因此,为优化用户体验,我们可以在用户上传过程中将图片压缩后再上传以优化用户的体验。
2. 实现
2.1 压缩jpg、png等常见的图片类型
压缩图片的过程实际上就是根据原图缩小宽高后绘制一张新的图片的过程。
/**
* 该方法接收3个参数
* file: 上传的文件
* type: 文件类型
* quality: 压缩的系数 0-1之间的值
*/
compressImg(file,type, quality) {
// 创建FileReader 解析文件
const reader = new FildReader();
render.onload = (e) => {
// 绘制新的图片
const image = new Image();
image.src = e.target.result as string;
image.onload = () => {
// 通过canvas绘制
const canvas = document.createEelement('canvas');
const context = canvas.getContext('2d');
canvas.width = image.width * quality;
canvas.height = image.height * quality;
context.drawImage(image, 0, 0, canvas.width, canvas.height);
// 将图片转成Base64
const base64 = canvas.toDataURL();
const arr = base64.split(",");
// 将Base64转成原始图片
const bstr = atob(arr[1]);
const n = bstr.length;
const u8arr = new Uint8Array(n);
while(n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let blob = new Blob([u8arr], {
type: type
});
// 将blob上传到服务器....
}
}
}
2.2 上传svg
使用上面的方式上传svg会发现,图片并不能正确的回显,那是因为上面那样的压缩方式会把svg图片压坏,导致无法回显。
压缩svg图片的时候需要借助compressorjs这个库来完成
import Compressor from 'compressorjs';
if (type === 'image/svg+xml') {
new Compressor(file, {
quality: 0.5,
maxWidth: 800,
success: (result: any) => {
blob = new Blob([result],{
type: 'image/svg+xml',
});
// 将blob传给服务器即可...
}
}
3. 上传gif
上传gif的难点在于gif是一个动图,借助gifjs这个库对gif压缩的时候发现 gif在压缩之后变成了一个静图,这样就不是我们想要的结果了。
所以我们需要将gif的动图切割,然后进行压缩,再组合起来变成动图。过程有点繁琐,这样做的性能并不好,还期待后续的优化。
if (type === 'image/gif') {
const gif = new GIF({
workers: 2,
quality: 10,
workerScript: 'assets/gif.worker.js',
repeat: -1
});
const frames = [];
const numFrames = 10;
const offset = 400;
// 对像素进行平移
for (let i = 0; i < numFrames; i++) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(image, 0, 0, canvas.width, canvas.height);
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
// 每帧平移的像素数
const index = (y * canvas.width + x) * 4;
const newY = (y + offset * i) % canvas.height; // 新的 Y 坐标位置
const newIndex = (newY * canvas.width + x) * 4;
imageData.data[newIndex] = imageData.data[index];
imageData.data[newIndex + 1] = imageData.data[index + 1];
imageData.data[newIndex + 2] = imageData.data[index + 2];
imageData.data[newIndex + 3] = imageData.data[index + 3];
}
}
// 获取平移像素点的图片
const frameData = new ImageData(new Uint8ClampedArray(imageData.data), canvas.width, canvas.height);
// 将压缩后的图片拼接在一个数组中
frames.push(frameData);
}
// 循环渲染
frames.forEach(frameData => {
gif.addFrame(frameData, { delay: 100 });
});
gif.on('finished', blob => {
// 得到blob 可以传给服务器了
})
}
这样循环像素的效率非常低,然后还要压缩图片,然后组合起来,压缩gif的效果并不是很好,暂未找到合适优化办法,期待后续改进