溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

微信小程序生成海報分享朋友圈的實現(xiàn)方法

發(fā)布時間:2020-10-08 01:22:31 來源:腳本之家 閱讀:199 作者:mrr 欄目:web開發(fā)

項目需求寫完有一段時間了,但是還是想回過來總結(jié)一下,一是對項目的回顧優(yōu)化等,二是對坑的地方做個記錄,避免以后遇到類似的問題。

需求

利用微信強大的社交能力通過小程序達(dá)到裂變的目的,拉取新用戶。

生成的海報如下

微信小程序生成海報分享朋友圈的實現(xiàn)方法

需求分析

1、利用小程序官方提供的api可以直接分享轉(zhuǎn)發(fā)到微信群打開小程序

2、利用小程序生成海報保存圖片到相冊分享到朋友圈,用戶長按識別二維碼關(guān)注公眾號或者打開小程序來達(dá)到裂變的目的

實現(xiàn)方案

一、分析如何實現(xiàn)

相信大家應(yīng)該都會有類似的迷惑,就是如何按照產(chǎn)品設(shè)計的那樣繪制成海報,其實當(dāng)時我也是不知道如何下手,認(rèn)真想了下得通過canvas繪制成圖片,這樣用戶保存這個圖片到相冊,就可以分享到朋友圈了。但是要繪制的圖片上面不僅有文字還有數(shù)字、圖片、二維碼等且都是活的,這個要怎么動態(tài)生成呢。認(rèn)真想了下,需要一點一點的將文字和數(shù)字,背景圖繪制到畫布上去,這樣通過api最終合成一個圖片導(dǎo)出到手機相冊中。

二、需要解決的問題

1、二維碼的動態(tài)獲取和繪制(包括如何生成小程序二維碼、公眾號二維碼、打開網(wǎng)頁二維碼)

2、背景圖如何繪制,獲取圖片信息

3、將繪制完成的圖片保存到本地相冊

4、處理用戶是否取消授權(quán)保存到相冊

三、實現(xiàn)步驟

這里我具體寫下圍繞上面所提出的問題,描述大概實現(xiàn)的過程

①首先創(chuàng)建canvas畫布,我把畫布定位設(shè)成負(fù)的,是為了不讓它顯示在頁面上,是因為我嘗試把canvas通過判斷條件動態(tài)的顯示和隱藏,在繪制的時候會出現(xiàn)問題,所以采用了這種方法,這里還有一定要設(shè)置畫布的大小。

<canvas canvas-id="myCanvas" ></canvas>

②創(chuàng)建好畫布之后,先繪制背景圖,因為背景圖我是放在本地,所以獲取 <canvas> 組件 canvas-id 屬性,通過 createCanvasContext 創(chuàng)建canvas的繪圖上下文 CanvasContext 對象。使用 drawImage 繪制圖像到畫布,第一個參數(shù)是圖片的本地地址,后面兩個參數(shù)是圖像相對畫布左上角位置的x軸和y軸,最后兩個參數(shù)是設(shè)置圖像的寬高。

const ctx = wx.createCanvasContext('myCanvas')

ctx.drawImage('/img/study/shareimg.png', 0, 0, 690, 1085)

③創(chuàng)建好背景圖后,在背景圖上繪制頭像,文字和數(shù)字。通過 getImageInfo 獲取頭像的信息,這里需要注意下在獲取的網(wǎng)絡(luò)圖片要先配置download域名才能生效,具體在小程序后臺設(shè)置里配置。

獲取頭像地址,首先量取頭像在畫布中的大小,和x軸Y軸的坐標(biāo),這里的result[0]是我用promise封裝返回的一個圖片地址

let headImg = new Promise(function (resolve) {
  wx.getImageInfo({
   src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`,
   success: function (res) {
   resolve(res.path)
   },
   fail: function (err) {
   console.log(err)
   wx.showToast({
    title: '網(wǎng)絡(luò)錯誤請重試',
    icon: 'loading'
   })
   }
  })
  })
let avatarurl_width = 60, //繪制的頭像寬度
 avatarurl_heigth = 60, //繪制的頭像高度
 avatarurl_x = 28, //繪制的頭像在畫布上的位置
 avatarurl_y = 36; //繪制的頭像在畫布上的位置
 ctx.save(); // 先保存狀態(tài) 已便于畫完圓再用
 ctx.beginPath(); //開始繪制
 //先畫個圓 前兩個參數(shù)確定了圓心 (x,y) 坐標(biāo) 第三個參數(shù)是圓的半徑 四參數(shù)是繪圖方向 默認(rèn)是false,即順時針
 ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);
 ctx.clip(); //畫了圓 再剪切 原始畫布中剪切任意形狀和尺寸。一旦剪切了某個區(qū)域,則所有之后的繪圖都會被限制在被剪切的區(qū)域內(nèi)
 ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推進去圖片

這里舉個例子說下如何繪制文字,比如我要繪制如下這個“字”,需要動態(tài)獲取前面字?jǐn)?shù)的總寬度,這樣才能設(shè)置“字”的x軸坐標(biāo),這里我本來是想通過 measureText 來測量字體的寬度,但是在iOS端第一次獲取的寬度值不對,關(guān)于這個問題,我還在微信開發(fā)者社區(qū)提了 bug ,所以我想用另一個方法來實現(xiàn),就是先獲取正常情況下一個字的寬度值,然后乘以總字?jǐn)?shù)就獲得了總寬度,親試是可以的。

微信小程序生成海報分享朋友圈的實現(xiàn)方法 

let allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325;
ctx.font = 'normal normal 30px sans-serif';
ctx.setFillStyle('#ffffff')
ctx.fillText('字', allReading, 150);

④繪制公眾號二維碼,和獲取頭像是一樣的,也是先通過接口返回圖片網(wǎng)絡(luò)地址,然后再通過 getImageInfo 獲取公眾號二維碼圖片信息

⑤如何繪制小程序碼,具體官網(wǎng)文檔也給出生成無限 小程序碼接口 ,通過生成的小程序可以打開任意一個小程序頁面,并且二維碼永久有效,具體調(diào)用哪個小程序二維碼接口有不同的應(yīng)用場景,具體可以看下官方文檔怎么說的,也就是說前端通過傳遞參數(shù)調(diào)取后端接口返回的小程序碼,然后繪制在畫布上(和上面寫的繪制頭像和公眾號二維碼一樣的)
ctx.drawImage('小程序碼的本地地址', x軸, Y軸, 寬, 高)

⑥最終繪制完把canvas畫布轉(zhuǎn)成圖片并返回圖片地址

wx.canvasToTempFilePath({
   canvasId: 'myCanvas',
   success: function (res) {
    canvasToTempFilePath = res.tempFilePath // 返回的圖片地址保存到一個全局變量里
    that.setData({
    showShareImg: true
    })
    wx.showToast({
    title: '繪制成功',
    })
   },
   fail: function () {
    wx.showToast({
    title: '繪制失敗',
    })
   },
   complete: function () {
    wx.hideLoading()
    wx.hideToast()
   }
   })

⑦保存到系統(tǒng)相冊;先判斷用戶是否開啟用戶授權(quán)相冊,處理不同情況下的結(jié)果。比如用戶如果按照正常邏輯授權(quán)是沒問題的,但是有的用戶如果點擊了取消授權(quán)該如何處理,如果不處理會出現(xiàn)一定的問題。所以當(dāng)用戶點擊取消授權(quán)之后,來個彈框提示,當(dāng)它再次點擊的時候,主動跳到設(shè)置引導(dǎo)用戶去開啟授權(quán),從而達(dá)到保存到相冊分享朋友圈的目的。

// 獲取用戶是否開啟用戶授權(quán)相冊
 if (!openStatus) {
  wx.openSetting({
  success: (result) => {
   if (result) {
   if (result.authSetting["scope.writePhotosAlbum"] === true) {
    openStatus = true;
    wx.saveImageToPhotosAlbum({
    filePath: canvasToTempFilePath,
    success() {
     that.setData({
     showShareImg: false
     })
     wx.showToast({
     title: '圖片保存成功,快去分享到朋友圈吧~',
     icon: 'none',
     duration: 2000
     })
    },
    fail() {
     wx.showToast({
     title: '保存失敗',
     icon: 'none'
     })
    }
    })
   }
   }
  },
  fail: () => { },
  complete: () => { }
  });
 } else {
  wx.getSetting({
  success(res) {
   // 如果沒有則獲取授權(quán)
   if (!res.authSetting['scope.writePhotosAlbum']) {
   wx.authorize({
    scope: 'scope.writePhotosAlbum',
    success() {
    openStatus = true
    wx.saveImageToPhotosAlbum({
     filePath: canvasToTempFilePath,
     success() {
     that.setData({
      showShareImg: false
     })
     wx.showToast({
      title: '圖片保存成功,快去分享到朋友圈吧~',
      icon: 'none',
      duration: 2000
     })
     },
     fail() {
     wx.showToast({
      title: '保存失敗',
      icon: 'none'
     })
     }
    })
    },
    fail() {
    // 如果用戶拒絕過或沒有授權(quán),則再次打開授權(quán)窗口
    openStatus = false
    console.log('請設(shè)置允許訪問相冊')
    wx.showToast({
     title: '請設(shè)置允許訪問相冊',
     icon: 'none'
    })
    }
   })
   } else {
   // 有則直接保存
   openStatus = true
   wx.saveImageToPhotosAlbum({
    filePath: canvasToTempFilePath,
    success() {
    that.setData({
     showShareImg: false
    })
    wx.showToast({
     title: '圖片保存成功,快去分享到朋友圈吧~',
     icon: 'none',
     duration: 2000
    })
    },
    fail() {
    wx.showToast({
     title: '保存失敗',
     icon: 'none'
    })
    }
   })
   }
  },
  fail(err) {
   console.log(err)
  }
  })
 }

總結(jié)

至此所有的步驟都已實現(xiàn),在繪制的時候會遇到一些異步請求后臺返回的數(shù)據(jù),所以我用promise和async和await進行了封裝,確保導(dǎo)出的圖片信息是完整的。在繪制的過程確實遇到一些坑的地方。比如初開始導(dǎo)出的圖片比例大小不對,還有用measureText測量文字寬度不對,多次繪制(可能受網(wǎng)絡(luò)原因)有時導(dǎo)出的圖片上的文字顏色會有誤差等。如果你也遇到一些比較坑的地方可以一起探討下做個記錄,下面附下完整的代碼

import regeneratorRuntime from '../../utils/runtime.js' // 引入模塊
const app = getApp(),
 api = require('../../service/http.js');
var ctx = null, // 創(chuàng)建canvas對象
 canvasToTempFilePath = null, // 保存最終生成的導(dǎo)出的圖片地址
 openStatus = true; // 聲明一個全局變量判斷是否授權(quán)保存到相冊
// 獲取微信公眾號二維碼
 getCode: function () {
 return new Promise(function (resolve, reject) {
  api.fetch('/wechat/open/getQRCodeNormal', 'GET').then(res => {
  console.log(res, '獲取微信公眾號二維碼')
  if (res.code == 200) {
   console.log(res.content, 'codeUrl')
   resolve(res.content)
  }
  }).catch(err => {
  console.log(err)
  })
 })
 },
 // 生成海報
 async createCanvasImage() {
 let that = this;
 // 點擊生成海報數(shù)據(jù)埋點
 that.setData({
  generateId: '點擊生成海報'
 })
 if (!ctx) {
  let codeUrl = await that.getCode()
  wx.showLoading({
  title: '繪制中...'
  })
  let code = new Promise(function (resolve) {
  wx.getImageInfo({
   src: codeUrl,
   success: function (res) {
   resolve(res.path)
   },
   fail: function (err) {
   console.log(err)
   wx.showToast({
    title: '網(wǎng)絡(luò)錯誤請重試',
    icon: 'loading'
   })
   }
  })
  })
  let headImg = new Promise(function (resolve) {
  wx.getImageInfo({
   src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`,
   success: function (res) {
   resolve(res.path)
   },
   fail: function (err) {
   console.log(err)
   wx.showToast({
    title: '網(wǎng)絡(luò)錯誤請重試',
    icon: 'loading'
   })
   }
  })
  })
  Promise.all([headImg, code]).then(function (result) {
  const ctx = wx.createCanvasContext('myCanvas')
  console.log(ctx, app.globalData.ratio, 'ctx')
  let canvasWidthPx = 690 * app.globalData.ratio,
   canvasHeightPx = 1085 * app.globalData.ratio,
   avatarurl_width = 60, //繪制的頭像寬度
   avatarurl_heigth = 60, //繪制的頭像高度
   avatarurl_x = 28, //繪制的頭像在畫布上的位置
   avatarurl_y = 36, //繪制的頭像在畫布上的位置
   codeurl_width = 80, //繪制的二維碼寬度
   codeurl_heigth = 80, //繪制的二維碼高度
   codeurl_x = 588, //繪制的二維碼在畫布上的位置
   codeurl_y = 984, //繪制的二維碼在畫布上的位置
   wordNumber = that.data.wordNumber, // 獲取總閱讀字?jǐn)?shù)
   // nameWidth = ctx.measureText(that.data.wordNumber).width, // 獲取總閱讀字?jǐn)?shù)的寬度
   // allReading = ((nameWidth + 375) - 325) * 2 + 380;
   // allReading = nameWidth / app.globalData.ratio + 325;
   allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325;
  console.log(wordNumber, wordNumber.toString().length, allReading, '獲取總閱讀字?jǐn)?shù)的寬度')
  ctx.drawImage('/img/study/shareimg.png', 0, 0, 690, 1085)
  ctx.save(); // 先保存狀態(tài) 已便于畫完圓再用
  ctx.beginPath(); //開始繪制
  //先畫個圓 前兩個參數(shù)確定了圓心 (x,y) 坐標(biāo) 第三個參數(shù)是圓的半徑 四參數(shù)是繪圖方向 默認(rèn)是false,即順時針
  ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);
  ctx.clip(); //畫了圓 再剪切 原始畫布中剪切任意形狀和尺寸。一旦剪切了某個區(qū)域,則所有之后的繪圖都會被限制在被剪切的區(qū)域內(nèi)
  ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 推進去圖片
  ctx.restore(); //恢復(fù)之前保存的繪圖上下文狀態(tài) 可以繼續(xù)繪制
  ctx.setFillStyle('#ffffff'); // 文字顏色
  ctx.setFontSize(28); // 文字字號
  ctx.fillText(that.data.currentChildren.name, 103, 78); // 繪制文字
  ctx.font = 'normal bold 44px sans-serif';
  ctx.setFillStyle('#ffffff'); // 文字顏色
  ctx.fillText(wordNumber, 325, 153); // 繪制文字
  ctx.font = 'normal normal 30px sans-serif';
  ctx.setFillStyle('#ffffff')
  ctx.fillText('字', allReading, 150);
  ctx.font = 'normal normal 24px sans-serif';
  ctx.setFillStyle('#ffffff'); // 文字顏色
  ctx.fillText('打敗了全國', 26, 190); // 繪制文字
  ctx.font = 'normal normal 24px sans-serif';
  ctx.setFillStyle('#faed15'); // 文字顏色
  ctx.fillText(that.data.percent, 154, 190); // 繪制孩子百分比
  ctx.font = 'normal normal 24px sans-serif';
  ctx.setFillStyle('#ffffff'); // 文字顏色
  ctx.fillText('的小朋友', 205, 190); // 繪制孩子百分比
  ctx.font = 'normal bold 32px sans-serif';
  ctx.setFillStyle('#333333'); // 文字顏色
  ctx.fillText(that.data.singIn, 50, 290); // 簽到天數(shù)
  ctx.fillText(that.data.reading, 280, 290); // 閱讀時長
  ctx.fillText(that.data.reading, 508, 290); // 聽書時長
  // 書籍閱讀結(jié)構(gòu)
  ctx.font = 'normal normal 28px sans-serif';
  ctx.setFillStyle('#ffffff'); // 文字顏色
  ctx.fillText(that.data.bookInfo[0].count, 260, 510); 
  ctx.fillText(that.data.bookInfo[1].count, 420, 532); 
  ctx.fillText(that.data.bookInfo[2].count, 520, 594); 
  ctx.fillText(that.data.bookInfo[3].count, 515, 710); 
  ctx.fillText(that.data.bookInfo[4].count, 492, 828); 
  ctx.fillText(that.data.bookInfo[5].count, 348, 858); 
  ctx.fillText(that.data.bookInfo[6].count, 212, 828); 
  ctx.fillText(that.data.bookInfo[7].count, 148, 726); 
  ctx.fillText(that.data.bookInfo[8].count, 158, 600); 
  ctx.font = 'normal normal 18px sans-serif';
  ctx.setFillStyle('#ffffff'); // 文字顏色
  ctx.fillText(that.data.bookInfo[0].name, 232, 530); 
  ctx.fillText(that.data.bookInfo[1].name, 394, 552); 
  ctx.fillText(that.data.bookInfo[2].name, 496, 614); 
  ctx.fillText(that.data.bookInfo[3].name, 490, 730); 
  ctx.fillText(that.data.bookInfo[4].name, 466, 850); 
  ctx.fillText(that.data.bookInfo[5].name, 323, 878); 
  ctx.fillText(that.data.bookInfo[6].name, 184, 850); 
  ctx.fillText(that.data.bookInfo[7].name, 117, 746); 
  ctx.fillText(that.data.bookInfo[8].name, 130, 621); 
  ctx.drawImage(result[1], codeurl_x, codeurl_y, codeurl_width, codeurl_heigth); // 繪制頭像
  ctx.draw(false, function () {
   // canvas畫布轉(zhuǎn)成圖片并返回圖片地址
   wx.canvasToTempFilePath({
   canvasId: 'myCanvas',
   success: function (res) {
    canvasToTempFilePath = res.tempFilePath
    that.setData({
    showShareImg: true
    })
    console.log(res.tempFilePath, 'canvasToTempFilePath')
    wx.showToast({
    title: '繪制成功',
    })
   },
   fail: function () {
    wx.showToast({
    title: '繪制失敗',
    })
   },
   complete: function () {
    wx.hideLoading()
    wx.hideToast()
   }
   })
  })
  })
 }
 },
 // 保存到系統(tǒng)相冊
 saveShareImg: function () {
 let that = this;
 // 數(shù)據(jù)埋點點擊保存學(xué)情海報
 that.setData({
  saveId: '保存學(xué)情海報'
 })
 // 獲取用戶是否開啟用戶授權(quán)相冊
 if (!openStatus) {
  wx.openSetting({
  success: (result) => {
   if (result) {
   if (result.authSetting["scope.writePhotosAlbum"] === true) {
    openStatus = true;
    wx.saveImageToPhotosAlbum({
    filePath: canvasToTempFilePath,
    success() {
     that.setData({
     showShareImg: false
     })
     wx.showToast({
     title: '圖片保存成功,快去分享到朋友圈吧~',
     icon: 'none',
     duration: 2000
     })
    },
    fail() {
     wx.showToast({
     title: '保存失敗',
     icon: 'none'
     })
    }
    })
   }
   }
  },
  fail: () => { },
  complete: () => { }
  });
 } else {
  wx.getSetting({
  success(res) {
   // 如果沒有則獲取授權(quán)
   if (!res.authSetting['scope.writePhotosAlbum']) {
   wx.authorize({
    scope: 'scope.writePhotosAlbum',
    success() {
    openStatus = true
    wx.saveImageToPhotosAlbum({
     filePath: canvasToTempFilePath,
     success() {
     that.setData({
      showShareImg: false
     })
     wx.showToast({
      title: '圖片保存成功,快去分享到朋友圈吧~',
      icon: 'none',
      duration: 2000
     })
     },
     fail() {
     wx.showToast({
      title: '保存失敗',
      icon: 'none'
     })
     }
    })
    },
    fail() {
    // 如果用戶拒絕過或沒有授權(quán),則再次打開授權(quán)窗口
    openStatus = false
    console.log('請設(shè)置允許訪問相冊')
    wx.showToast({
     title: '請設(shè)置允許訪問相冊',
     icon: 'none'
    })
    }
   })
   } else {
   // 有則直接保存
   openStatus = true
   wx.saveImageToPhotosAlbum({
    filePath: canvasToTempFilePath,
    success() {
    that.setData({
     showShareImg: false
    })
    wx.showToast({
     title: '圖片保存成功,快去分享到朋友圈吧~',
     icon: 'none',
     duration: 2000
    })
    },
    fail() {
    wx.showToast({
     title: '保存失敗',
     icon: 'none'
    })
    }
   })
   }
  },
  fail(err) {
   console.log(err)
  }
  })
 }
 },

總結(jié)

以上所述是小編給大家介紹的微信小程序生成海報分享朋友圈的實現(xiàn)方法,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復(fù)大家的!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI