電商項目中須要將本身小店的商品帶上本身的小程序碼生成海報,保存到本地,而後分享到萬能的朋友圈,QQ空間,微博等等來廣而告之...
以下圖,三種海報格式輪播展現,左滑右滑切換到海報,點擊下面保存圖片按鈕,將當前海報保存到手機相冊
html
按照思路逐步實現:前端
1.商品信息經過導航事件傳遞到海報頁,在此我使用的是模擬數據;
2.用戶信息經過本地存儲wx.setStorageSync 到緩存。git
// index.js //事件處理函數 navToShare: function () { // 模擬數據 var data = { thumb_images: [ 'https://cbu01.alicdn.com/img/ibank/2018/544/692/8567296445_882293189.400x400.jpg', 'https://cbu01.alicdn.com/img/ibank/2018/971/643/8581346179_882293189.400x400.jpg', 'https://cbu01.alicdn.com/img/ibank/2018/184/392/8567293481_882293189.400x400.jpg' ], name: '2018夏季新款鏤空圓領蝙蝠短袖t恤女裝韓版寬鬆棉小衫上衣批發潮', price: 198, } wx.navigateTo({ url: '../poster/poster?data=' + encodeURIComponent(JSON.stringify(data)) }) },
3.在海報頁面onLoad函數的參數中獲取商品信息
4.在海報頁面獲取本地緩存中的用戶信息wx.getStorageSync
5.由於canvas繪製圖片不支持跨域圖片,因此先使用getImageInfo將網絡圖片返回圖片的本地路徑,github
// poster.js onLoad: function(options) { var data = JSON.parse(decodeURIComponent(options.data)); var userinfo; // 獲取本地存儲的用戶頭像和暱稱 userinfo = wx.getStorageSync('userInfo'); console.log('用戶信息', userinfo) // 渲染頁面 this.setData({ avatar_url: userinfo.avatarUrl, nickname: userinfo.nickName, thumb_images: data.thumb_images, pro_price: data.price, pro_name: data.name, }) // 保存網絡圖片到本地 用於canvas繪製圖片 wx.getImageInfo({ src: userinfo.avatarUrl, success: (res) => { tmpAvatarUrl = res.path; } }); // 保存產品圖到本地 用於canvas繪製圖片 var thumbs = data.thumb_images; tmpThumbs = []; // 先清空,再添加新的產品圖 thumbs.forEach((item, i) => { wx.getImageInfo({ src: item, success: (res) => { tmpThumbs.push(res.path) } }) }); },
7.小程序碼由後端生成,前端經過POST請求將data傳入,返回小程序碼url,使用 wx.getImageInfo保存到本地canvas
// 封裝後的POST方法 wxRequest.postRequest(url, data).then(res => { if (res.data.error_code == 0) { // 保存小程序碼到本地 用於canvas繪製圖片 wx.getImageInfo({ src: res.data.qrcode, success: (result) => { this.setData({ poster_qrcode: result.path }) } }); } })
在這個項目中我是將頁面渲染和canvas繪製分開的,由於小程序單位rpx自動適配各類設備屏幕。而canvas繪製單位是px。我沒有作px和rpx之間的計算,保存px單位固定大小的圖片也不錯。小程序
<view class='poster_swiper'> <swiper bindchange="shareChange" current="{{current}}" circular="{{circular}}" previous-margin="100rpx" next-margin="100rpx" class="swiper_share"> <swiper-item class="swiper_item1"> // 根據設計渲染頁面 </swiper-item> <swiper-item class="swiper_item2" wx:if="{{thumb_images.length>1}}"> // 根據設計渲染頁面 </swiper-item> <swiper-item class="swiper_item3" wx:if="{{thumb_images.length>2}}"> // 根據設計渲染頁面 </swiper-item> </swiper> </view>
這裏要用到swiper的幾個屬性列出來後端
current | Number | 0 | 當前所在滑塊的 index | |
circular | Boolean | false | 是否採用銜接滑動 | |
previous-margin | String | "0px" | 前邊距,可用於露出前一項的一小部分,接受 px 和 rpx 值 | 1.9.0 |
next-margin | String | "0px" | 後邊距,可用於露出後一項的一小部分,接受 px 和 rpx 值 | 1.9.0 |
bindchange | EventHandle | current 改變時會觸發 change 事件,event.detail = {current: current, source: source} |
1.在wxml中添加canvas組件,設置canvas-id以便於wx.createCanvasContext繪製畫布api
<canvas class='canvas-poster' canvas-id='canvasposter'></canvas>
定義樣式固定定位到可視區之外,不影響可視區展現。跨域
.canvas-poster { position: fixed; width: 280px; height: 450px; top: 100%; left: 100%; overflow: hidden; }
三種海報分別繪製,具體看註釋緩存
/*一張產品圖*/ drawPosterOne: function() { var ctx = wx.createCanvasContext('canvasposter'); // ctx.clearRect(0, 0, 280, 450); /* 繪製背景*/ ctx.rect(0, 0, 280, 450); ctx.setFillStyle('white'); ctx.fillRect(0, 0, 280, 450); /*繪製店名*/ ctx.setFontSize(16); ctx.setFillStyle('#333'); ctx.textAlign = "center"; ctx.fillText(this.data.nickname + '的小店', 140, 70); ctx.restore(); /*繪製產品圖*/ ctx.drawImage(tmpThumbs[0], 35, 90, 210, 210); /* 繪製產品名稱背景*/ ctx.setFillStyle('#FF8409'); ctx.fillRect(35, 300, 210, 60); /*繪製產品名稱*/ ctx.setFontSize(12); ctx.setFillStyle('#ffffff'); ctx.textAlign = "left"; ctx.fillText(this.data.pro_name.substr(0, 18), 45, 322); ctx.restore(); ctx.setFontSize(12); ctx.setFillStyle('#ffffff'); ctx.textAlign = "left"; ctx.fillText(this.data.pro_name.substr(18, 20), 45, 344); ctx.restore(); /* 繪製線框*/ ctx.setLineDash([1, 3], 1); ctx.beginPath(); ctx.moveTo(35, 375); ctx.lineTo(160, 375); ctx.moveTo(35, 435); ctx.lineTo(160, 435); ctx.setStrokeStyle('#979797'); ctx.stroke(); ctx.restore(); /*繪製文字*/ ctx.setFontSize(14); ctx.setFillStyle('#333333'); ctx.textAlign = "left"; ctx.fillText('¥', 35, 400); ctx.setFontSize(18); ctx.fillText(this.data.pro_price, 50, 400); ctx.setFontSize(11); ctx.setFillStyle('#666666'); ctx.fillText(this.data.poster_qrtext, 35, 420); ctx.restore(); /*繪製二維碼*/ ctx.drawImage(this.data.poster_qrcode, 185, 370, 60, 60); ctx.restore(); /*圓形頭像*/ ctx.save() ctx.beginPath(); ctx.arc(140, 30, 20, 0, 2 * Math.PI) ctx.setFillStyle('#fff') ctx.fill() ctx.clip() ctx.drawImage(tmpAvatarUrl, 120, 10, 40, 40) ctx.restore() ctx.draw(false, this.getTempFilePath); }, /*兩張產品圖*/ drawPosterTwo: function() { var ctx = wx.createCanvasContext('canvasposter'); /* 繪製背景*/ ctx.rect(0, 0, 280, 450); ctx.setFillStyle('white'); ctx.fillRect(0, 0, 280, 450); /*繪製店名*/ ctx.setFontSize(14); ctx.setFillStyle('#333'); ctx.textAlign = "left"; ctx.fillText(this.data.nickname + '的小店', 65, 36); ctx.restore(); /* 繪製虛線框*/ ctx.setLineDash([4, 1], 1); ctx.beginPath(); ctx.moveTo(25, 60); ctx.lineTo(255, 60); ctx.moveTo(25, 325); ctx.lineTo(255, 325); ctx.setStrokeStyle('#979797'); ctx.stroke(); ctx.restore(); /*繪製產品名稱*/ ctx.setFontSize(12); ctx.setFillStyle('#333'); ctx.textAlign = "left"; ctx.fillText(this.data.pro_name.substr(0, 13), 25, 82); ctx.setFontSize(12); ctx.setFillStyle('#333'); ctx.fillText(this.data.pro_name.substr(13, 12) + '...', 25, 100); ctx.restore(); /*繪製文字*/ ctx.setFontSize(14); ctx.setFillStyle('#333333'); ctx.textAlign = "left"; ctx.fillText('¥', 190, 90); ctx.setFontSize(16); ctx.fillText(this.data.pro_price, 205, 90); ctx.restore(); ctx.setFontSize(10); ctx.setFillStyle('#666666'); ctx.textAlign = "center"; ctx.fillText(this.data.poster_qrtext, 140, 420); ctx.restore(); /*繪製產品圖*/ ctx.drawImage(tmpThumbs[0], 25, 115, 110, 150); ctx.drawImage(tmpThumbs[1], 145, 115, 110, 150); ctx.restore(); /*繪製文字*/ ctx.setFontSize(12); ctx.setFillStyle('#333333'); ctx.textAlign = "left"; ctx.fillText(this.data.slogan1, 25, 290); ctx.fillText(this.data.slogan2, 25, 308); ctx.restore(); /*繪製二維碼*/ ctx.drawImage(this.data.poster_qrcode, 110, 330, 70, 70); ctx.restore(); /*圓形頭像*/ ctx.save() ctx.beginPath(); ctx.arc(35, 30, 20, 0, 2 * Math.PI) ctx.setFillStyle('#fff') ctx.fill() ctx.clip() ctx.drawImage(tmpAvatarUrl, 15, 10, 40, 40) ctx.restore() ctx.draw(false, this.getTempFilePath); }, /*三張產品圖*/ drawPosterThree: function() { var ctx = wx.createCanvasContext('canvasposter'); /* 繪製背景*/ ctx.rect(0, 0, 280, 450); ctx.setFillStyle('white'); ctx.fillRect(0, 0, 280, 450); /*繪製店名*/ ctx.setFontSize(16); ctx.setFillStyle('#333'); ctx.textAlign = "center"; ctx.fillText(this.data.nickname + '的小店', 140, 70); ctx.restore(); /* 繪製虛線框*/ ctx.beginPath() ctx.setLineDash([4, 1], 1); ctx.beginPath(); ctx.moveTo(20, 230); ctx.lineTo(145, 230); ctx.lineTo(145, 305); ctx.lineTo(40, 305); /*左下角圓角 ctx.arcTo( , 左下角左邊座標,左上角左邊座標,半徑)*/ ctx.arcTo(20, 305, 20, 230, 20); ctx.moveTo(20, 230); ctx.lineTo(20, 285); ctx.setStrokeStyle('#333333') ctx.stroke() ctx.setStrokeStyle('#979797'); ctx.stroke(); ctx.restore(); /*繪製產品名稱*/ ctx.setFontSize(12); ctx.setFillStyle('#333'); ctx.textAlign = "left"; ctx.fillText(this.data.pro_name.substr(0, 9), 30, 250); ctx.setFontSize(12); ctx.setFillStyle('#333'); ctx.fillText(this.data.pro_name.substr(9, 8) + '...', 30, 268); ctx.restore(); /*繪製文字*/ ctx.setFontSize(14); ctx.setFillStyle('#333333'); ctx.textAlign = "left"; ctx.fillText('¥', 30, 290); ctx.setFontSize(16); ctx.fillText(this.data.pro_price, 45, 290); ctx.restore(); ctx.setFontSize(10); ctx.setFillStyle('#666666'); ctx.textAlign = "center"; ctx.fillText(this.data.poster_qrtext, 140, 420); ctx.restore(); /*繪製產品圖*/ ctx.drawImage(tmpThumbs[0], 20, 90, 125, 125); ctx.drawImage(tmpThumbs[1], 160, 90, 100, 100); ctx.drawImage(tmpThumbs[2], 160, 205, 100, 100); ctx.restore(); ctx.restore(); /*繪製二維碼*/ ctx.drawImage(this.data.poster_qrcode, 110, 330, 70, 70); ctx.restore(); /*圓形頭像*/ ctx.save() ctx.beginPath(); ctx.arc(140, 30, 20, 0, 2 * Math.PI) ctx.setFillStyle('#fff') ctx.fill() ctx.clip() ctx.drawImage(tmpAvatarUrl, 120, 10, 40, 40) ctx.restore() ctx.draw(false, this.getTempFilePath); },
繪製中用到的數據以下
var tmpAvatarUrl = ""; /*用於繪製頭像*/ var tmpThumbs = []; /*用於繪製產品圖*/ var drawing = false; /*避免屢次點擊保存按鈕*/ Page({ /** * 頁面的初始數據 */ data: { circular: true, // swiper 是否採用銜接滑動 current: 0, // swiper 當前所在滑塊的 index avatar_url: '', // 渲染頭像 nickname: '', // 渲染暱稱 poster_qrcode: '/images/poster_qrcode.png', // 小程序碼 poster_qrtext: '長按識別,便可查看商品', pro_name: '', //產品名 pro_price: '', // 產品價格 slogan1: '個人小店上新了,', // 標語 1 slogan2: '快來一塊兒快來一塊兒看看吧!', // 標語 2 thumb_images: [] // 渲染圖片 },
//獲取臨時路徑 getTempFilePath: function() { wx.canvasToTempFilePath({ canvasId: 'canvasposter', success: (res) => { this.saveImageToPhotosAlbum(res.tempFilePath) } }) },
//保存至相冊 saveImageToPhotosAlbum: function(imgUrl) { if (imgUrl) { wx.saveImageToPhotosAlbum({ filePath: imgUrl, success: (res) => { wx.showToast({ title: '保存成功', icon: 'success', duration: 2000 }) drawing = false }, fail: (err) => { wx.showToast({ title: '保存失敗', icon: 'none', duration: 2000 }) drawing = false } }) }else{ wx.showToast({ title: '繪製中……', icon: 'loading', duration: 3000 }) } },
注意canvas繪製須要時間,因此設置 drawing 防止繪製被打斷
1.首先根據 change 事件設置current
shareChange: function(e) { if (e.detail.source == 'touch') { this.setData({ current: e.detail.current }) } },
2.經過點擊按鈕執行savePoster保存海報到手機相冊
<view class="common_btn" catchtap="savePoster"> <text>保存圖片</text> </view>
判斷是否獲取相冊受權,已得到權限直接繪製,若未得到權限需提示用戶前去設置受權
/*保存海報到手機相冊*/ savePoster: function(e) { var that = this; var current = this.data.current; //獲取相冊受權 wx.getSetting({ success(res) { if (!res.authSetting['scope.writePhotosAlbum']) { wx.authorize({ scope: 'scope.writePhotosAlbum', success() { //這裏是用戶贊成受權後的回調 that.drawPoster(current); }, fail() { //這裏是用戶拒絕受權後的回調 wx.showModal({ title: '提示', content: '若不打開受權,則沒法將圖片保存在相冊中!', showCancel: true, cancelText: '去受權', cancelColor: '#000000', confirmText: '暫不受權', confirmColor: '#3CC51F', success: function(res) { if (res) { wx.openSetting({ //調起客戶端小程序設置界面,返回用戶設置的操做結果。 }) } else { // console.log('用戶點擊取消') } } }) } }) } else { //用戶已經受權過了 that.drawPoster(current); } } }) },
3.根據current判斷當前海報繪製對應海報
/* 繪製海報*/ drawPoster: function(current) { if(drawing){ wx.showToast({ title: '繪製中……', icon: 'loading', duration: 3000 }) }else{ drawing = true; // loading // 根據swiper當前所在滑塊的 index判斷繪製對應海報 switch (current) { case 0: this.drawPosterOne() break; case 1: this.drawPosterTwo() break; case 2: this.drawPosterThree() break; } } },
保存到手機相冊的海報以下: