前言:
繼續接着上次用戶提交個性化頭像圖片,服務端審核的需求,這次是前端的巨坑部分。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分鍾從入門到吐槽:

