微信小程序使用canvas进行按需图片列表压缩
2020-08-20 本文已影响0人
羞羞的小桔
概述
在微信小程序中使用canvas来对图片进行压缩的例子网上已经有很多了,但是网上的例子都只能对单张图片进行压缩。由于官方接口wx.chooseImage()在实际生产常常被用于进行多张图片的选择,所以我们需要一个方法来对图片列表进行压缩。
可是已经有了压缩单张图片的方法,那么循环调用它不就能对图片列表进行压缩了吗?实际上不是。由于在微信小程序中只能够使用wx.createCanvasContext()接口通过wxml中定义的canvas-id来获取canvas对象,继而使用这个对象来进行绘图。因此当循环调用图片压缩方法时,就会导致每次循环都使用同一个canvas对象
绘图、导出图片,并且由于canvas的绘图方法是异步进行
的,因此最终获取到的压缩后的图片列表就会出现紊乱(亲测)。除非你愿意在wxml中定义多个id不同的canvas。
循环压缩图片行不通,因此本文介绍了使用递归
的方法来对图片列表进行压缩。由于使用递归来实现列表的循环压缩,因此能够避免canvas异步绘图方法带来的影响。
其实说到递归,思路就很明显了,不过其中也有一些细节和坑值得记录一下。
具体实现
实现步骤
- 使用wx.chooseImage()接口选择图片,接口返回图片地址列表。
- 递归调用图片压缩方法。
- 图片压缩方法中使用wx.getImageInfo()接口获取图片长宽信息。
- 根据图片长宽信息计算图片大小,并根据目标图片大小对图片的长宽进行裁剪,以达到压缩图片大小的目的。
- 根据canvas-id获取canvas绘图上下文,绘制最终图片后将画布内容导出为图片。
代码实现
wxml代码
- 在wxml文件末尾定义canvas组件,通过设置绝对定位使canvas离屏显示。
- 设置canvas-id来获取canvas绘图上下文。
<canvas canvas-id="canvas" style="width:{{cWidth}}px;height:{{cHeight}}px;position: absolute;left:-1000px;top:-1000px;"></canvas>
js代码
- 在data中定义cWidth和cHeight属性用以动态设置canvas的长宽。
- pics数组用以存放和显示压缩后的图片列表。
data: { cWidth: 0, cHeight: 0, pics: []}
- wx.chooseImage()的处理。在页面的js文件中调用chooseImage()方法选择图片,继而将选择的图片传递到cutBase64()方法中进行压缩处理。
chooseImg() {
// 限制最多选择5张图片
const count = 5 - this.data.pics.length;
wx.chooseImage({
count: count,
// 在小程序的api中说明设置sizeType=compressed后会返回压缩后的图片,但测试发现,只有图片大小超出某个范围之后才会对图片进行压缩处理
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: res => {
const urls = res.tempFilePaths;
// 参数分别为:图片url数组、wxml中设置的canvasId、递归所需index、设置canvas长宽的方法、处理压缩后图片的回调方法
util.cutBase64(urls, 'canvasId', 0, this.setCanvas, this.setPics);
}
})
},
/**
* 通过这个方法设置canvas的长宽,设置后调用回调方法。
* 如果不使用回调方法,而是直接设置canvas长宽后就进行下一步处理,最终得到的图片大小可能会有跟预期有所差异
*/
setCanvas(width, height, callback) {
this.setData({
cWidth: width,
cHeight: height
}, callback);
},
/**
* 对压缩后的图片进行处理
*/
setPics(url) {
// 这样处理是因为在我的使用过程中需要得到图片的base64数据,也可以根据实际需求对url做出不同的处理
wx.getFileSystemManager().readFile({
filePath: url, //选择图片返回的相对路径
encoding: 'base64', //编码格式
success: res => {
const base64 = 'data:image/png;base64,' + res.data;
// 打印出压缩后的图片大小
console.log(base64.length);
this.setData({
pics: this.data.pics.push(base64);
});
}
})
}
Util工具类:cutBase64()
const cutBase64 = (urls, canvasId, index, setCanvas, callback) => {
if (index == urls.length) {
return;
}
// 根据urls及index获取图片信息
wx.getImageInfo({
src: urls[index],
success: (res) => {
// 设置初始裁剪比例,当图片大小大于设置阈值时,则对图片长宽按次比例进行裁剪
let ratio = 2;
let canvasWidth = res.width;
let canvasHeight = res.height;
// 根据比例对图片长宽进行裁剪,若裁剪后图片大小依然大于设置阈值时,则提高裁剪比例继续对图片进行裁剪
// 此处假定需要将图片裁剪至200kb以内,因此 阈值 = 200 * 1024 = 204800
while (canvasWidth * canvasHeight > 204800) {
canvasWidth = Math.trunc(res.width / ratio);
canvasHeight = Math.trunc(res.height / ratio);
ratio++;
}
// 调用setCanvas()方法设置canvas的长宽,设置完成后开始绘图,否则可能会出现"fail canvas is empty"的错误
setCanvas(canvasWidth, canvasHeight, () => {
// 获取canvasId对应的绘图上下文对象
let ctx = wx.createCanvasContext(canvasId);
ctx.drawImage(urls[index], 0, 0, canvasWidth, canvasHeight);
// draw()的第一个参数为false表示不保留canvas上一次的绘制结果
// 这里设置100毫秒的延迟是为了在canvas绘图完成后再获取图片信息,否则获取到的图片可能是空白
ctx.draw(false, setTimeout(() => {
// 获取canvas绘制内容并转为图片
wx.canvasToTempFilePath({
canvasId: canvasId,
destWidth: canvasWidth,
destHeight: canvasHeight,
fileType: 'jpg', // 可选的参数有:jpg、png
success: res => {
// 图片导出成功后调用回调
callback(res.tempFilePath);
// 进行下一次递归,由于此时canvas的绘制与图片的导出都已经结束了,因此不会对canvas后续的绘制、导出产生影响
cutBase64(urls, canvasId, ++index, setCanvas, callback);
}
})
}, 100));
});
}
})
}