[入门到吐槽系列] 微信小程序 上传图片 前端压缩 保存到云存储 使用Canvas新API的巨坑!
前言:
继续接着上次用户提交个性化头像图片,服务端审核的需求,这次是前端的巨坑部分。10分钟避坑指南分享。
避坑过程分享:
我们的小程序是这个样子的:
现在的需求是,长按头像后,可以选择自定义的图片,更换头像。这么需求就特么简单一句话,又足足搞了我12个小时。
技术思路:
- 使用wx.chooseImage选择图片
- 使用canvas对图片进行尺寸变更,前端压缩
- 使用canvasToTempFilePath对压缩有的图片保存到临时目录
- 使用wx.cloud.uploadFile提交到云存储,然后异步让微信校验
选择用户自定义图片 chooseMedia:
当你想使用chooseImage的时候,突然发现IDE提示这个接口不再维护。恩。。。不详的预感。毕竟现在网上搜的一大堆文档,都是用chooseImage,所以现在最新代码是:
wx.chooseMedia({ count: 1, sizeType: ['compressed'], // original 原图 compressed 压缩图 sourceType: ['album', 'camera'], // album 从相册选图 camera 使用相机 }).then(e1 => { console.log('图像处理', 'chooseMedia', e1); if (e1.tempFiles.length <= 0) return; });
获取图片的信息 getImageInfo:
接下来要进行前端压缩,先获取图片的尺寸信息:
wx.chooseMedia({ count: 1, sizeType: ['compressed'], // original 原图 compressed 压缩图 sourceType: ['album', 'camera'], // album 从相册选图 camera 使用相机 }).then(e1 => { console.log('图像处理', 'chooseMedia', e1); if (e1.tempFiles.length <= 0) return; wx.getImageInfo({ src: e1.tempFiles[0].tempFilePath, success: function (e2) { const imgWidth = e2.width; const imgHeight = e2.height; const dpr = wx.getSystemInfoSync().pixelRatio * 2; const imgW = Math.trunc(imgWidth / dpr); const imgH = Math.trunc(imgW / imgWidth * imgHeight); console.log('图像处理', 'getImageInfo', e2, dpr, [imgWidth, imgHeight], [imgW, imgH]); }, fail: function (res) { console.log(res.errMsg) }, });
这里使用了屏幕的pixelRatio后,发现图片还是不够小,只是用来做头像,所以我们就使用2倍的pixelRatio。
巨坑1——获取Canvas:
当我看资料,说用wx.createCanvasContext的时候,竟然提示被废弃了。瞬间网上9成的资料都变成废话。只要继续慢慢摸索,使用wx.createSelectorQuery:
实际代码,首先在页面声明一个canvas,id是必须的,其他的canvas-id之类已经没意义了。类型type是2d。
<canvas id="avatarCanvas" canvasId="avatarCanvas" type="2d" style="width:{{cWidth}}px;height:{{cHeight}}px;position: absolute;left:-10000px;top:-10000px;"></canvas>
然后后端使用选择器获取这个canvas:
wx.chooseMedia({ count: 1, sizeType: ['compressed'], // original 原图 compressed 压缩图 sourceType: ['album', 'camera'], // album 从相册选图 camera 使用相机 }).then(e1 => { console.log('图像处理', 'chooseMedia', e1); if (e1.tempFiles.length <= 0) return; wx.getImageInfo({ src: e1.tempFiles[0].tempFilePath, success: function (e2) { const imgWidth = e2.width; const imgHeight = e2.height; const dpr = wx.getSystemInfoSync().pixelRatio * 2; const imgW = Math.trunc(imgWidth / dpr); const imgH = Math.trunc(imgW / imgWidth * imgHeight); console.log('图像处理', 'getImageInfo', e2, dpr, [imgWidth, imgHeight], [imgW, imgH]); const query = wx.createSelectorQuery(); query.select('#avatarCanvas').fields({ node: true, size: true }).exec(e3 => { console.log('图像处理', 'createSelectorQuery', e3); const canvas = e3[0].node; canvas.width = imgW; canvas.height = imgH; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, imgW, imgH); that.setData({ cWidth: imgW, cHeight: imgH, }); }); }, fail: function (res) { console.log(res.errMsg) }, }); });
必须注意,页面要声明type=2d,否则选择器返回的node是个null!小坑。
然后canvas一定要设置width、height,同时也要设置setData,修改页面的绑定的style的宽度高度,否则图片会拉伸。
超级无敌巨坑2——canvas显示上传的图片:
传统看资料,把图片画到canvas就是ctx.drawImage,现在用了新的API之后,这些方法都废了。新的api是:
const img = canvas.createImage(); img.onload = (e4) => { console.log('图像处理', 'onload', e4); ctx.drawImage(img, 0, 0, imgW, imgH); } img.src = e2.path;
这里面有个小坑,drawImage第一个参数必须是创建的img对象节点,不再是旧api的图片地址了。
那么放入整个代码里面:
wx.chooseMedia({ count: 1, sizeType: ['compressed'], // original 原图 compressed 压缩图 sourceType: ['album', 'camera'], // album 从相册选图 camera 使用相机 }).then(e1 => { console.log('图像处理', 'chooseMedia', e1); if (e1.tempFiles.length <= 0) return; wx.getImageInfo({ src: e1.tempFiles[0].tempFilePath, success: function (e2) { const imgWidth = e2.width; const imgHeight = e2.height; const dpr = wx.getSystemInfoSync().pixelRatio * 2; const imgW = Math.trunc(imgWidth / dpr); const imgH = Math.trunc(imgW / imgWidth * imgHeight); console.log('图像处理', 'getImageInfo', e2, dpr, [imgWidth, imgHeight], [imgW, imgH]); const query = wx.createSelectorQuery(); query.select('#avatarCanvas').fields({ node: true, size: true }).exec(e3 => { console.log('图像处理', 'createSelectorQuery', e3); const canvas = e3[0].node; canvas.width = imgW; canvas.height = imgH; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, imgW, imgH); that.setData({ cWidth: imgW, cHeight: imgH, }); const img = canvas.createImage(); img.onload = (e4) => { console.log('图像处理', 'onload', e4); ctx.drawImage(img, 0, 0, imgW, imgH); } img.src = e2.path; }); }, fail: function (res) { console.log(res.errMsg) }, }); });
这个时候,启动调试,会发现报错了!!!
tmp/wxf65e9ae5f68283d2.o6zAJs5h4IkyHaGS7_j6gUPGTR9c.arwyj04Eq2ok341457e272957e237fa21d743912f60b.jpg:1 GET http://tmp/wxf65e9ae5f68283d2.o6zAJs5h4IkyHaGS7_j6gUPGTR9c.arwyj04Eq2ok341457e272957e237fa21d743912f60b.jpg net::ERR_PROXY_CONNECTION_FAILED
对于这个问题的讨论,再这里可以看到详情:
https://developers.weixin.qq.com/community/develop/doc/00040e150384902e280a3a85e56800?highLine=drawimage%252C%2520%25E4%25B8%25B4%25E6%2597%25B6%25E5%259C%25B0%25E5%259D%2580
大概意思是image对象无法访问临时路径。然后要修改下开发环境的代理:
小编认为这是个腾讯的bug,但是也困扰了接近3个小时。反正改后就没事了,可以看到日志输出:
改后就不报错了,可以进入image的onload事件。
压缩图片提交云存储:
最后简单了,获取压缩后的图片,提交云存储,核心代码:
const cfgSave = { fileType: "jpg", quality: 0.5, width: imgW, height: imgH, destWidth: imgW, destHeight: imgH, canvas: canvas, }; wx.canvasToTempFilePath({ ...cfgSave, }).then(e5 => { console.log("图像处理", 'canvasToTempFilePath', e5); wx.cloud.uploadFile({ cloudPath: 'avatars/' + new Date().getTime() + ".jpg", filePath: e5.tempFilePath, }).then(e6 => { console.log("图像处理", 'uploadFile', e6); }); }).catch(err => { console.error(err); })
整段完整的代码:
wx.chooseMedia({ count: 1, sizeType: ['compressed'], // original 原图 compressed 压缩图 sourceType: ['album', 'camera'], // album 从相册选图 camera 使用相机 }).then(e1 => { console.log('图像处理', 'chooseMedia', e1); if (e1.tempFiles.length <= 0) return; wx.getImageInfo({ src: e1.tempFiles[0].tempFilePath, success: function (e2) { const imgWidth = e2.width; const imgHeight = e2.height; const dpr = wx.getSystemInfoSync().pixelRatio; const imgW = Math.min(640, Math.trunc(imgWidth / dpr)); const imgH = Math.trunc(imgW / imgWidth * imgHeight); console.log('图像处理', 'getImageInfo', e2, dpr, [imgWidth, imgHeight], [imgW, imgH]); const query = wx.createSelectorQuery(); query.select('#avatarCanvas').fields({ node: true, size: true }).exec(e3 => { console.log('图像处理', 'createSelectorQuery', e3); const canvas = e3[0].node; canvas.width = imgW; canvas.height = imgH; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, imgW, imgH); that.setData({ cWidth: imgW, cHeight: imgH, }); const img = canvas.createImage(); img.onload = (e4) => { console.log('图像处理', 'onload', e4); ctx.drawImage(img, 0, 0, imgW, imgH); const cfgSave = { fileType: "jpg", quality: 0.5, width: imgW, height: imgH, destWidth: imgW, destHeight: imgH, canvas: canvas, }; wx.canvasToTempFilePath({ ...cfgSave, }).then(e5 => { console.log("图像处理", 'canvasToTempFilePath', e5); wx.cloud.uploadFile({ cloudPath: 'avatars/' + new Date().getTime() + ".jpg", filePath: e5.tempFilePath, }).then(e6 => { console.log("图像处理", 'uploadFile', e6); }); }).catch(err => { console.error(err); }) } img.src = e2.path; }); }, fail: function (res) { console.log(res.errMsg) }, }); });
云存储的结果:
整个流程分享完毕,接下来就是接到提交图片给腾讯鉴黄流程了,可以参考上一篇文章。
欢迎大家关注咱们公众号:辰同学技术 微信公众号,同样会分享各种技术10分钟从入门到吐槽: