在小程序开发中,生成海报可谓是个高频需求,这里我总结了下常见的步骤:
- 主要有文字及背景绘制,用户头像和小程序二维码
- 注:该案例实现基于mpvue框架,如果使用小程序自有框架或者uniapp等框架需要做少量调整
资源准备
小程序二维码
在生成海报之前首先要获取小程序二维码,这通常是服务端生成,在这个例子中我们就采用云开发来代替。 云开发的好处就是 FAAS = Functions as a Service , 可以绕开繁琐的后端搭建,让开发专注业务逻辑(对于前端而言则又向全干工程师迈进了一步)
引用大帅老师的一张图,在云开发的模式下,以下传统部署均可省略
developers.weixin.qq.com/miniprogram…
- 接口A,可以接受 pages/index/index?param=128byte (10万次)?userid=xxx&from=123213
- 接口C,官方已经不推荐使用了,方形不建议使用了。
- 接口B,必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index 根路径前不要 填加 / ,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面
云函数代码如下: getMpCode/index.js
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
try {
//微信官方接口,生成小程序码 (这里为展示采用接口A,实际项目推荐接口B)
const result = await cloud.openapi.wxacode.get({
"path": 'page/index/index',
"width": 430,
"isHyaline":true
})
return result
} catch (err) {
return err
}
}
调用云函数获取二维码:
//用promise封装主要是为了之后在使用时避免发生回调地狱(回调中嵌套回调,层数太深,可读性可维护性差)
getQrcode(){
return new Promise((resolve,reject)=>{
wx.cloud.callFunction({
name:"getMpCode",
//获取的数据是buffer类型,需要加上文件头
success:(res)=>{
const src="data:image/jpg;base64,"+ wx.arrayBufferToBase64(res.result.buffer);
resolve(src)
},
fail:(err)=>{
reject(err)
}
});
})
},
用户头像
获取用户头像需要用户授权,我这边的做法是将获取到的用户信息保存在本地,绘制海报时如果判断本地无用户头像信息则会弹框引导用户授权。
//判断本地是否有用户头像信息
checkUserInfo(){
const userInfoProfile = wx.getStorageSync("userProfile");
if(!userInfoProfile){
return false
}
return true
},
//绘制海报时先判断是否有头像信息
drawPoster(){
const self=this;
if(!self.checkUserInfo()){
//如果不存在用户信息,则需要弹框引导用户授权获取
return wx.showModal({
title: '提示',
content: '请先授权获取头像信息',
async success (res) {
if (res.confirm) {
//用户授权后获取用户信息
await self.getUserprofile()
self.drawPoster();
} else if (res.cancel) {
}
}
})
};
//...
}
//获取用户信息
getUserprofile(){
return new Promise((resolve,reject)=>{
//该接口需要按钮触发,不能直接在生命周期中使用
wx.getUserProfile({
desc:'获取你的昵称,头像',
success:res=>{
//获取成功后存入本地
wx.setStorage({
key: "userProfile",
data: res.userInfo
});
resolve()
},
fail: (res)=>{
console.log(`fail`,res)
reject()
}
})
})
},
//从本地获取用户头像
getHeadImage(){
return new Promise((resolve,reject)=>{
//从storage中获取
const userProfile=wx.getStorageSync(`userProfile`);
if(!userProfile){
return reject()
}
let {avatarUrl=""}=userProfile;
resolve(avatarUrl)
})
},
背景图
这里采用的是将图片事先存入云存储中,然后再调用 wx.getImageInfo 将网络图片下载到本地以备后续作图所用
getBgImg(){
const self=this
return new Promise((resolve,reject)=>{
//获取云存储中的图片
self.getImgInfo('cloud://cloud1-8gp38tt58f1cad0e.636c-cloud1-8gp38tt58f1cad0e-1306912889/mdata/img/weiwuxian2.jpg').then(res=>{
console.log(`cloudImg`,res)
//{errMsg: "getImageInfo:ok", width: 794, height: 794, type: "jpeg", orientation: "up",path: "http://tmp/wsz3Rlfyo5yj279d60b79b9c747d19a192f79e7e9cb8.jpg",type:"jpeg",width:794}
if(res && res.path){
resolve(res.path)
}else{
reject()
}
})
})
},
getImgInfo(src){
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: src,
success: resolve,
fail: reject
})
})
},
画布的样式
由于在获取临时路劲保存图片的时候用一倍的canvas保存的图片会很模糊,我们需要对canvas画布进行多倍处理,可以以像素比为倍率,这样比较好处理,这里用的是像素比,具体如下
结构样式
<template>
<view class="sharePoster">
<view>
<view class="canvasWrap">
<canvas id="myCanvas" type="2d" style="height:450px;width:300px;"></canvas>
<img :src="poster" class="poster-img" style="height:450px;width:300px; margin-top:20rpx;">
</view>
<button type="primary" style="width:500rpx" @click="drawPoster">绘制海报</button>
<button type="primary" @click="btnSavePoster" style="width:500rpx;margin-top:20rpx">保存海报</button>
</view>
</view>
</template>
<style scoped lang="less">
.canvasWrap{
display:flex;
flex-direction:column;
align-items:center;
padding-bottom:20rpx;
#myCanvas{
position:absolute;
left:-1000px; //canvas隐藏在可视区外
}
}
</style>
画布适配
onReady(){
this.drawPoster();
},
drawPoster(){
const query = wx.createSelectorQuery()
const self=this;
///...用户授权获取头像信息部分
//获取canvas实例 和 上下文对象
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec(async (res) => {
self.canvas = res[0].node
self.ctx = self.canvas.getContext('2d')
//屏幕像素比
const dpr = wx.getSystemInfoSync().pixelRatio
self.canvas.width = res[0].width * dpr
self.canvas.height = res[0].height * dpr
//画布放大后绘制比例也相应放大,这样绘图时就能按原尺寸计算
self.ctx.scale(dpr, dpr)
//...绘制背景 文字, 获取头像和二维码,绘制头像和二维码
})
},
绘制背景和文字
drawPoster(){
const query = wx.createSelectorQuery()
const self=this;
//...判断本地信息,引导用户授权获取头像部分
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec(async (res) => {
//...画布适配部分
//背景
self.ctx.fillStyle='#F5F5F5'
self.ctx.fillRect(0,0,300,450)
self.ctx.fillStyle='pink'
self.ctx.fillRect(0, 0, 300, 300)
//文字
self.ctx.textBaseline='top'
self.ctx.textAlign='left'
self.ctx.fillStyle='#000'
self.ctx.fontSize=120+'px'
self.ctx.fillText("我正在掘金写文章 快来帮我点赞吧!", 20, 320)
//绘制分割线
self.ctx.moveTo(20, 340)
self.ctx.lineTo(280, 340)
self.ctx.strokeStyle = '#333'
self.ctx.stroke()
//...获取头像和二维码,绘制头像和二维码,转化成图片
})
}
绘制头像,二维码和背景图
获取头像,二维码和背景图
前文已叙,代码如下
drawPoster(){
const query = wx.createSelectorQuery()
const self=this;
//...判断本地信息,引导用户授权获取头像部分
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec(async (res) => {
//...画布适配部分
//...背景及文字绘制部分
wx.showLoading({
title: '海报生成中'
});
//获取头像和二维码和背景图
const result=await Promise.all([self.getHeadImage(),self.getQrcode(),self.getBgImg()]);
console.log(result)
//["https://thirdwx.qlogo.cn/mmopen/vi_32/Al8jy0dq1soJ…OiczAdv0PBXCt5erf1WZU90csaMsXjR3ERib9BIFJskTQ/132", "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAa4A…ra2vrIXKqra2tra2th8j/Cwh3EyS5PLNGAAAAAElFTkSuQmCC", "http://tmp/wsz3Rlfyo5yj279d60b79b9c747d19a192f79e7e9cb8.jpg"]
//...绘制头像和二维码,转化成图片
})
}
//获取用户头像
getHeadImage(){
return new Promise((resolve,reject)=>{
//从storage中获取
const userProfile=wx.getStorageSync(`userProfile`);
if(!userProfile){
return reject()
}
let {avatarUrl=""}=userProfile;
resolve(avatarUrl)
})
},
//获取小程序二维码
getQrcode(){
return new Promise((resolve,reject)=>{
wx.cloud.callFunction({
name:"getMpCode",
success:(res)=>{
const src="data:image/jpg;base64,"+ wx.arrayBufferToBase64(res.result.buffer);
console.log(`scr`,src)
resolve(src)
},
fail:(err)=>{
reject(err)
}
});
})
},
//获取背景图
getBgImg(){
const self=this
return new Promise((resolve,reject)=>{
//获取云存储中的图片
self.getImgInfo('cloud://cloud1-8gp38tt58f1cad0e.636c-cloud1-8gp38tt58f1cad0e-1306912889/mdata/img/weiwuxian2.jpg').then(res=>{
console.log(`cloudImg`,res)
//{errMsg: "getImageInfo:ok", width: 794, height: 794, type: "jpeg", orientation: "up",path: "http://tmp/wsz3Rlfyo5yj279d60b79b9c747d19a192f79e7e9cb8.jpg",type:"jpeg",width:794}
if(res && res.path){
resolve(res.path)
}else{
reject()
}
})
})
},
绘制头像,二维码和背景图
drawPoster(){
const query = wx.createSelectorQuery()
const self=this;
//...判断本地信息,引导用户授权获取头像部分
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec(async (res) => {
//...画布适配部分
//...背景及文字绘制部分
wx.showLoading({
title: '海报生成中'
});
//获取头像和二维码和背景图
const result=await Promise.all([self.getHeadImage(),self.getQrcode(),self.getBgImg()]);
//...绘制头像和二维码和背景图
await Promise.all([self.drawHeadImg(result[0])], self.drawQRcode2(result[1]),self.drawBgImg(result[2]))
wx.hideLoading();
})
}
//绘制头像
drawHeadImg(img){
const self=this
return new Promise((resolve,reject)=>{
//头像img因为是网络图片,一般先要 通过wx.getImageInfo到本地(其实经过我的测试,这步省略也不会报错)
self.getImgInfo(img).then(res=>{
let image=self.canvas.createImage();
image.src=res.path;
image.onload=()=>{
self.ctx.save()
self.ctx.fillStyle='#fff'
//因为要绘制圆形头像 先作出一个圆形裁切区域,然后再绘制矩形的头像图片,最终效果就是圆形头像
self.ctx.beginPath()
self.ctx.arc(70, 400, 50, 0, 2 * Math.PI)
self.ctx.clip()
self.ctx.drawImage(image, 20, 350,100,100);
self.ctx.restore()
resolve()
}
})
})
},
getImgInfo(src){
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: src,
success: resolve,
fail: reject
})
})
},
//绘制二维码图像
drawQRcode2(bufferData){
const self=this;
return new Promise((resolve,reject)=>{
let image=self.canvas.createImage();
//加载buffer数据
image.src=bufferData;
image.onload=()=>{
self.ctx.drawImage(image, 160, 350,100,100);
resolve()
}
})
},
//绘制背景图
drawBgImg(src){
const self=this;
return new Promise((resolve,reject)=>{
let image=self.canvas.createImage();
image.src=src;
image.onload=()=>{
self.ctx.drawImage(image, 0, 0, 300, 300);
resolve()
}
})
},
将canvas图像转化成图片
drawPoster(){
const query = wx.createSelectorQuery()
const self=this;
//...判断本地信息,引导用户授权获取头像部分
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec(async (res) => {
//...画布适配部分
//...背景及文字绘制部分
wx.showLoading({
title: '海报生成中'
});
//获取头像和二维码和背景图
const result=await Promise.all([self.getHeadImage(),self.getQrcode(),self.getBgImg()]);
//...绘制头像和二维码和背景图
await Promise.all([self.drawHeadImg(result[0])], self.drawQRcode2(result[1]),self.drawBgImg(result[2]))
wx.hideLoading();
//将canvas转化成临时路径,用imge加载生成图片呈现在页面
self.canvas2Img()
})
}
canvas2Img(){
const self=this
setTimeout(() => {
//将画布保存成临时路径 供展示
wx.canvasToTempFilePath({
//因为是2d所以需要传入整个canvas节点
canvas:self.canvas,
success: (res) => {
//将路径保存下来通过image在页面上展示
self.poster = res.tempFilePath;
},
fail:(res)=>{
console.log(`fail`,res)
}
},self)
}, 200)
}
触发按钮将海报保存至本地
btnSavePoster(){
const self=this;
//将本地路径的图片保存至相册
wx.saveImageToPhotosAlbum({
filePath: self.poster,
success: (result) => {
wx.showToast({
title: '海报已保存,快去分享给好友吧。',
icon: 'none'
})
},
//图片保存至相册需要用户授权,如果保存失败拉起授权弹框
fail:(err)=>{
console.log(err)
if(err.errMsg === "saveImageToPhotosAlbum:fail:auth denied" || err.errMsg === "saveImageToPhotosAlbum:fail auth deny" || err.errMsg === "saveImageToPhotosAlbum:fail authorize no response") {
wx.showModal({
title: '提示',
content: '需要您授权保存相册',
showCancel: false,
success: modalSuccess => {
wx.openSetting({
success(settingdata) {
if (settingdata.authSetting['scope.writePhotosAlbum']) {
console.log('获取权限成功,给出再次点击图片保存到相册的提示。')
}else {
console.log('获取权限失败,给出不给权限就无法正常使用的提示')
}
}
})
}
})
}
}
})
},