项目场景
项目中需要用户在小程序中上传图片,使用vant中的组件上传,并且在上传服务器前对图片进行压缩,以减小存储空间占用。
问题分析
微信小程序从基础库2.7版本开始,使用新的 Canvas api, 网上很多博客中所给出的解决方案在新的api下不能工作,主要的变化有如下几点:
- Canvas对象的获取: 需要通过 SelectorQuery 获取,从基础库2.9版本开始,原先的
wx.createCanvasContext
创建canvas对象的方式被废除,新版本下canvas组件支持type参数来表明画布的类型,具体实现如下:
const query = that.createSelectorQuery();
query.select('#canvas')
.fields({ node: true, size: true })
.exec(res2 => {
const canvas = res2[0].node;
const ctx = canvas.getContext('2d');
});
- CanvasContext对象绘图: 旧的api使用
CanvasContext.draw
进行绘图,新版本的api为了和Web端统一,废弃了该api,使用时会报错,相应的需要使用CanvasContext.drawImage
来进行绘图,第一个参数传入的不再是图像的url,而是Image
对象,可以使用Canvas.createImage
来创建。
此外,在实践过程中,出现canvas绘制的图像内容为空的问题,对应的临时图片地址也无法在微信开发者工具中打开,经过debug发现,Image.onload对应的回调函数里的ctx.drawImage
是一个异步执行的过程,如果图像还没有绘制完,后续调用canvasToTempFilePath
拿到的临时地址对应的图像是不完整的,因此打开内容为空,无法正常显示。因此,这里需要将ctx.drawImage
过程用promise
包装一下,执行完毕后,再获取临时地址,实现见下面的drawImg
方法。
解决方案
wxml文件,canvas需要通过定位样式来避免出现在屏幕可视区域。
<!-- vant文件上传组件 -->
<van-uploader upload-text="上传美食图片" file-list="{{ previewPic }}" max-count="1" deletable="{{ true }}"
bind:after-read="afterRead" bind:delete="deletePic" />
<!-- 微信canvas画布组件 -->
<canvas type="2d" id="canvas" style="width:{{cWidth}}px;height:{{cHeight}}px; position: absolute;left:-1000px;top:-1000px;"></canvas>
js文件
// 文件选择
afterRead: function (event) {
let that = this;
const { file } = event.detail;
// 单张为对象,多张为数组
let url = file.url;
var index = url.lastIndexOf(".");
//获取后缀
var ext = url.substr(index + 1);
var date = new Date();
if (!file.size || file.type != "image") {
wx.showToast({
title: '图片存在异常',
icon: 'none'
})
} else if(file.size > 1024*500){ // 大于500k开始压缩
let name = date.getTime() + '.' + ext;
that.compressByCanvas(url, name);
}
}
drawImg: function (ctx, imgObj, cwidth, cheight) {
return new Promise(resolve => {
setTimeout(function (image) {
ctx.drawImage(imgObj, 0, 0, cwidth, cheight);
resolve('done');
}, 500);
})
},
//压缩并获取图片
compressByCanvas: function (fileUrl, name) {
let that = this;
wx.getImageInfo({
src: fileUrl,
success: function (res1) {
var ratio = 2;
var canvasWidth = res1.width //图片原始长宽
var canvasHeight = res1.height
while (canvasWidth > 300 || canvasHeight > 300) {// 保证宽高在400以内
canvasWidth = Math.trunc(res1.width / ratio)
canvasHeight = Math.trunc(res1.height / ratio)
ratio++;
}
that.setData({
cWidth: canvasWidth,
cHeight: canvasHeight
})
//----------绘制图形并取出图片路径--------------
const query = that.createSelectorQuery();
query.select('#canvas')
.fields({ node: true, size: true })
.exec(res2 => {
const canvas = res2[0].node;
const ctx = canvas.getContext('2d');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const imgObj = canvas.createImage();
imgObj.src = fileUrl;
imgObj.onload = () => {
that.drawImg(ctx, imgObj, canvasWidth, canvasHeight)
.then(res => {
wx.canvasToTempFilePath({
canvas: canvas,
success: function (res) {
console.log(res.tempFilePath);
}
}, that)
})
}
})
},
fail: function (res) {
console.log(res.errMsg);
}
});
},