場景及需求
在小程序開發過程中,經常需要實現保存某個頁面為帶小程序碼的二維碼海報圖片到本地,然后用于分享或者發朋友圈等操作。
主要技術點及小程序相關api
技術注意事項
- 小程序的canvas與H5 canvas使用api大部分一致,但由于小程序中沒有DOM節點的概念,所以不能使用很多現成的工具庫
- 小程序canvas默認寬度300px、高度225px
- 小程序canvas相關的api中單位為px,并非rpx,所以在業務實現過程中需要處理適配
- 小程序canvas對跨域圖片不支持,需要先將圖片緩存到本地
技術點
- wx.getImageInfo()
- wx.getSystemInfo()
- wx.canvasToTempFilePath()
- wx.saveImageToPhotosAlbum()
- canvas渲染相關api
整體流程
1.先獲取所有圖片資源,線上圖片需要緩存到本地,使用圖片的本地路徑做渲染
2.獲取設備信息,根據設備寬度計算出寬度因子x
3.繪制canvas
4.將canvas轉化為圖片,將圖片保存到本機
實現
寬度因子x及元素寬度的尺寸計算
rpx(responsive pixel): 可以根據屏幕寬度進行自適應。規定屏幕寬為750rpx。如在 iPhone6 上,屏幕寬度為375px,共有750個物理像素,則750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
參考文檔: 小程序-WXSS-尺寸單位
由文檔可以看出不同的設備,寬度是不同數值的px,但是均為750rpx,由此可以利用750rpx計算出寬度因子x:
設備寬度 / 750
以設計稿寬度750rpx為例,則在不同設備下,設計稿寬度y在不同設備下的px寬度均為:
Y = y * x
Y = y * (設備寬度 / 750)
由此,在實現中,canvas標簽寬高需要設置為變量,且使用微信提供的 wx.getSystemInfo() 接口獲取設備寬度信息后,進行計算
<canvas canvas-id="qrcode-canvas" :style="{width: canvas.width + 'px', height: canvas.height + 'px'}"></canvas>
復制代碼
// wx.js
// 獲取設備基本信息
export function wxGetSystemInfo () {return new Promise(async function (resolve, reject) {wx.getSystemInfo({success: function (res) {resolve(res);},fail: function (err) {reject(err);}})});
}
// demo.vue
// 獲取手機基本信息
async getPhoneSystemInfo () {let _this = this;let systemInfoRes = await wxGetSystemInfo();_this.canvas.width = systemInfoRes.screenWidth;// 設計稿寬高為 750 * 912_this.canvas.height = 912 / 750 * _this.canvas.width;_this.storageQrcode();
},
復制代碼
獲取遠程圖片緩存到本地使用
- api
- wx.getImageInfo()
- 參考文檔: wx.getImageInfo()
注意:如果是本地圖片,即'static'文件夾中的圖片,如'/static/logo.png'經過wx.getImageInfo返回的path會省去開頭的'/',即'static/logo.png',這會導致拿不到資源,所以本地圖片不需要調用wx.getImageInfo()進行本地緩存
// wx.js
// 獲取圖片基本信息
export function wxGetImgInfo (imgUrl) {return new Promise(async function (resolve, reject) {wx.getImageInfo({src: imgUrl,success: function (res) {resolve(res);},fail: function (err) {reject(err);}})});
}
// demo.vue
// 緩存遠程圖片
async storageQrcode () {let _this = this;// 背景圖url轉path// let bgRes = await wxGetImgInfo(_this.bgImg);// _this.imgPath.bgImg = bgRes.path;// Logo url轉path// let logoRes = await wxGetImgInfo(_this.logo);// _this.imgPath.logo = logoRes.path;// 頭像 url轉path// let headerImgRes = await wxGetImgInfo(_this.cardDetail.header);// _this.imgPath.headerImg = headerImgRes.path;// 二維碼 url轉pathlet qrCodeRes = await wxGetImgInfo(_this.qrCode);_this.imgPath.qrCode = qrCodeRes.path;console.log(_this.imgPath);// _this.initCanvas(_this.canvas.width);
},
復制代碼
繪制canvas
canvas繪制主要用到了圖片繪制、文字繪制,圖片繪制及文字繪制的時候,需要引入上文說到的寬度因子x進行計算
圖片繪制
- canvasContext.drawImage
- 參考文檔: canvasContext.drawImage
這里的圖片繪制之前先計算寬度因子
let _this = this;// variableVal即為上文拿到的設備寬度const variableNum = variableVal / 750; // 根據設備寬度算出一個rpx為多少pxconst ctx = wx.createCanvasContext('qrcode-canvas');// 清除畫布上矩形的內容ctx.clearRect(0, 0, 0, 0);// 繪制上部card背景圖const bgImgDesc = {url: _this.imgPath.bgImg,left: 0,top: 0,width: 750 * variableNum,height: 912 * variableNum};ctx.drawImage(bgImgDesc.url, bgImgDesc.left, bgImgDesc.top, bgImgDesc.width, bgImgDesc.height);ctx.draw();
復制代碼
文字的繪制
- canvasContext.setFillStyle - 設置顏色
- canvasContext.setFontSize - 設置大小
- canvasContext.fillText - 填充文本
const nameDesc = {text: _this.cardDetail.name,fontSize: 36 * variableNum,color: '#4F5E6F',left: 102 * variableNum,top: 200 * variableNum
};
ctx.setFillStyle(nameDesc.color);
ctx.setFontSize(nameDesc.fontSize);
ctx.fillText(nameDesc.text, nameDesc.left, nameDesc.top);
ctx.draw();
復制代碼
canvas轉圖片并保存到本地
- wx.canvasToTempFilePath - canvas轉圖片
- 參考文檔: wx.canvasToTempFilePath
- wx.saveImageToPhotosAlbum - 保存圖片到本地
- 參考文檔: wx.saveImageToPhotosAlbum
注意點
tip: wx.canvasToTempFilePath() 在 draw 回調里調用該方法才能保證圖片導出成功。
由于導出圖片需要在canvas繪制圖片的draw()方法回調中使用才能,所以我們在繪制canvas的時候直接轉canvas為圖片,然后將路徑存下來,點擊下載的時候,再直接拿圖片路徑進行下載操作。
// wx.js
// canvas畫布轉圖片
export function wxCanvasToTempFilePath (canvasObj) {return new Promise(async function (resolve, reject) {wx.canvasToTempFilePath({x: canvasObj.x,y: canvasObj.y,width: canvasObj.width,height: canvasObj.height,destWidth: canvasObj.destWidth,destHeight: canvasObj.destHeight,canvasId: canvasObj.canvasId,fileType: canvasObj.fileType ? canvasObj.fileType : 'png',success: function (res) {resolve(res);},fail: function (err) {reject(err);}})});
}
// demo.vue
// 繪制canvas
initCanvas () {let _this = this;// 繪制canvas......ctx.draw(false, function () {_this.saveImg();});
},
// 將canvas轉為圖片
async saveImg () {let _this = this;const canvasObj = {x: 0,y: 0,width: _this.canvas.width,height: _this.canvas.height,destWidth: _this.canvas.width * 4,destHeight: _this.canvas.height * 4,canvasId: 'qrcode-canvas',fileType: 'png'};let imgRes = await wxCanvasToTempFilePath(canvasObj);_this.qrCodeImgPath = imgRes.tempFilePath;
},
復制代碼
下載圖片到本地
<canvas canvas-id="qrcode-canvas" :style="{width: canvas.width + 'px', height: canvas.height + 'px'}"></canvas>
<button type="primary" plain="true" @click="downLoadImg"> 保存二維碼 </button>
復制代碼
// wx.js
// 保存圖片到本地
export function wxSaveImageToPhotosAlbum (filePath) {return new Promise(async function (resolve, reject) {wx.saveImageToPhotosAlbum({filePath: filePath,success: function (res) {resolve(res);},fail: function (err) {reject(err);}})});
}
// demo.vue
// 保存圖片到本機
async downLoadImg () {let _this = this;let saveRes = await wxSaveImageToPhotosAlbum(_this.qrCodeImgPath);if (saveRes.errMsg === 'saveImageToPhotosAlbum:ok') {wx.showToast({duration: 3000,icon: 'none',title: '保存圖片成功!',mask: true});} else {wx.showToast({duration: 3000,icon: 'none',title: '保存圖片失敗,請重試!',mask: true});}
},
復制代碼
demo代碼已經放在 './demo' 文件夾,歡迎交流
總結
在業務實現中,我們只要把業務流程進行分割,然后一步一步去實現,捋明白流程之后各個擊破很多第一反應去查找已有的庫來實現的功能自己實現起來也很簡單。
在某種意義上,自己弄明白原理之后去實現反而更加輕松,更加得心應手。
-- LucaLJX: github:https://github.com/LucaLJX