近日接触到微信小程序canvas绘制海报,经过一番折腾,算是能保存了,记录一下吧。
新版本Api接口
<!-- canvas.wxml -->
<!-- width,height是根据设计图及rpx与px像素比计算出来的宽高-->
<!-- wx.getSystemInfo({
success: (res) => {
if(res.model.indexOf("iPhone X") > -1){
this.globalData.distance = 36
}
this.globalData.rpx = res.windowWidth / 750
this.globalData.width = 635 * (res.windowWidth / 750)
this.globalData.height = 974 * (res.windowWidth / 750)
},
})-->
<canvas type="2d" id="myCan" style="width: {{width}}px;height:{{height}}px"></canvas>
生成海报
saveImg(res) {
const index = res.detail
wx.showLoading({
title: '海报生成中'
})
// 新接口 canvas2d
query.select('#myCan')
.fields({
node: true,
size: true
})
.exec((res) => {
console.log(res[0].node)
const canvas = res[0].node
const ctx = canvas.getContext('2d')
const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = res[0].width * dpr
canvas.height = res[0].height * dpr
ctx.scale(dpr, dpr)
saveCanvers(canvas, ctx, this.data.images[index], this.data.postersInfo.title, this.data.postersInfo.desc, this.data.postersInfo.desc1, this.data.avatar, '入秋的番薯', this.data.miniCode)
})
},
方法封装
async function saveCanvers(canvas, ctx, headImg, title, desc, desc1, codeImg) {
const App = getApp()
let rpx = App.globalData.rpx
// 海报解释下面向上移动的距离 如果两行解释 那么不上移 如果一行解释 那么上移35
let upMove = desc1 ? 0 : 35
// 背景色
ctx.fillStyle = '#ffffff'
// canvas大小
ctx.fillRect(0, 0, 638 * rpx, (974 - upMove) * rpx)
// 绘制主图
await downLoadDrow(canvas, ctx, headImg, 0, 0, 638 * rpx, 558 * rpx)
// 标题
ctx.font = 36 * rpx + 'px'
ctx.fillStyle = '#373B3E'
ctx.fillText(title, 30 * rpx, 608 * rpx, 589 * rpx)
// 解释
ctx.font = 24 * rpx + 'px'
ctx.fillStyle = '#9D9D9D'
ctx.fillText(desc, 30 * rpx, 648 * rpx, 589 * rpx)
desc1 && ctx.fillText(desc1, 30 * rpx, 684 * rpx, 589 * rpx)
// 虚线
ctx.strokeStyle = '#DEDEDE'
ctx.setLineDash([3, 3]);
ctx.beginPath();
ctx.moveTo(30 * rpx, (708 - upMove) * rpx);
ctx.lineTo(610 * rpx, (708 - upMove) * rpx);
ctx.stroke();
// 圆形头像
ctx.save()
ctx.beginPath()
ctx.arc(72 * rpx, (808 - upMove) * rpx, 42 * rpx, 0, 2 * Math.PI)
// 保证原型填充无bug
ctx.fill()
ctx.clip()
await downLoadDrow(canvas, ctx, userInfo.avatarUrl, 30 * rpx, (766 - upMove) * rpx, 84 * rpx, 84 * rpx)
ctx.restore()
// 用户昵称
ctx.font = 32 * rpx + 'px'
ctx.fillStyle = '#000000'
ctx.fillText(userInfo.nickName, 134 * rpx, (813 - upMove) * rpx, 200 * rpx)
// 邀请扫码
ctx.font = 24 * rpx + 'px'
ctx.fillText('邀您扫码体验', 30 * rpx, (888 - upMove) * rpx, 200 * rpx)
// 房车小程序
ctx.fillText('上汽大通原厂房车小程序', 30 * rpx, (930 - upMove) * rpx, 300 * rpx)
// 小程序码
await downLoadDrow(canvas, ctx, codeImg, 400 * rpx, (738 - upMove) * rpx, 206 * rpx, 206 * rpx)
setTimeout(() => {
wx.canvasToTempFilePath({
canvas: canvas,
success: (can) => {
wx.getSetting({
success(res) {
if (!res.authSetting['scope.writePhotosAlbum']) { //判断权限
wx.authorize({ //获取权限
scope: 'scope.writePhotosAlbum',
success() {
saveImg(can.tempFilePath)
}
})
} else {
saveImg(can.tempFilePath)
}
}
})
},
fail: (err) => {
wx.showToast({
title: '保存失败,请稍后重试',
icon: 'none'
})
return false
}
})
}, 500)
}
function downLoadDrow(canvas, ctx, url, x, y, w, h) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: url,
success: (res) => {
const img = canvas.createImage();
img.src = res.path; //微信请求返回头像
img.onload = () => {
console.log(img)
ctx.drawImage(img, x, y, w, h);
img.src = ""
resolve()
}
},
fail: (err) => {
wx.hideLoading()
wx.showToast({
title: '下载图片失败,请稍后重试',
icon: 'none'
})
reject()
return false
}
})
})
}
function saveImg(path) {
wx.hideLoading()
wx.saveImageToPhotosAlbum({
filePath: path,
success: (res) => {
wx.showToast({
title: '已保存到相册',
icon: 'success',
duration: 2000
})
},
fail: (err) => {
wx.showToast({
title: '保存失败',
icon: 'none'
});
}
})
}
老是在绘制的时候闪退,内存溢出,也不知道怎么解决,网上找了点方法,但好像都不行,也可能没找到重点,不得已,再来一套老版本canvas
老版本canvas
<canvas canvas-id="myCan" style="width: {{width}}px;height:{{height}}px"></canvas>
生成图片
saveImg(res) {
// 组件里面传递的图片索引 不在细说 可忽略
const index = res.detail
wx.showLoading({
title: '海报生成中'
})
let ctx = wx.createCanvasContext("myCan")
saveCanversOld(ctx, this.data.postersInfo.swiper[index], this.data.postersInfo.title, this.data.postersInfo.desc, this.data.postersInfo.desc1, this.data.postersInfo.buffer)
},
方法封装
async function saveCanversOld(ctx, headImg, title, desc, desc1, codeImg) {
let userInfo = wx.getStorageSync('userInfo')
const App = getApp()
let rpx = App.globalData.rpx
let upMove = desc1 ? 0 : 35
// 背景色
ctx.setFillStyle('#ffffff')
// canvas大小
ctx.fillRect(0, 0, 638 * rpx, (974 - upMove) * rpx)
// 绘制主图
await downLoadDrowOld(ctx, headImg, 0, 0, 638 * rpx, 558 * rpx, 'headImg')
// 标题
ctx.setFontSize(36 * rpx)
ctx.setFillStyle('#373B3E')
ctx.fillText(title, 30 * rpx, 608 * rpx, 589 * rpx)
// 解释
ctx.setFontSize(24 * rpx)
ctx.setFillStyle('#9D9D9D')
ctx.fillText(desc, 30 * rpx, 648 * rpx, 589 * rpx)
desc1 && ctx.fillText(desc1, 30 * rpx, 684 * rpx, 589 * rpx)
// 虚线
ctx.setStrokeStyle('#DEDEDE')
ctx.setLineDash([6, 5]);
ctx.beginPath();
ctx.moveTo(30 * rpx, (708 - upMove) * rpx);
ctx.lineTo(610 * rpx, (708 - upMove) * rpx);
ctx.stroke();
// 圆形头像
ctx.save()
ctx.beginPath()
ctx.arc(72 * rpx, (808 - upMove) * rpx, 42 * rpx, 0, 2 * Math.PI)
// 保证原型填充无bug
ctx.fill()
ctx.clip()
await downLoadDrowOld(ctx, userInfo && userInfo.avatarUrl || config.defaultImg, 30 * rpx, (766 - upMove) * rpx, 84 * rpx, 84 * rpx)
ctx.restore()
// 用户昵称
ctx.setFontSize(32 * rpx)
ctx.setFillStyle('#000000')
ctx.fillText(userInfo && userInfo.nickName || config.defaultUser, 134 * rpx, (813 - upMove) * rpx, 200 * rpx)
// 邀请扫码
ctx.setFontSize(24 * rpx)
ctx.fillText('邀您扫码体验', 30 * rpx, (888 - upMove) * rpx, 200 * rpx)
// 房车小程序
ctx.fillText('上汽大通原厂房车小程序', 30 * rpx, (930 - upMove) * rpx, 300 * rpx)
// 小程序码
await downLoadDrowOld(ctx, codeImg, 400 * rpx, (738 - upMove) * rpx, 206 * rpx, 206 * rpx, 'miniCode')
ctx.draw(false, () => {
wx.canvasToTempFilePath({
fileType: 'jpg',
canvasId: 'myCan',
success: (res) => {
console.log(res.tempFilePath)
saveImg(res.tempFilePath)
},
fail: (err) => {
console.log(err)
}
})
})
}
// 下载图片
function downLoadDrowOld(ctx, url, x, y, w, h, type = '') {
return new Promise((resolve, reject) => {
if (type == 'miniCode') {
//声明文件系统
const fs = wx.getFileSystemManager();
//随机定义路径名称
var times = new Date().getTime();
var codeimg = wx.env.USER_DATA_PATH + '/' + times + '.png';
//将base64图片写入
fs.writeFile({
filePath: codeimg,
data: url,
encoding: 'base64',
success: (res) => {
//写入成功了的话,新的图片路径就能用了
ctx.drawImage(codeimg, x, y, w, h)
resolve()
// 绘制成功之后要释放 内存
setTimeout(() => {
fs.unlink({
filePath: codeimg,
success: (res) => {
console.log('清除文件成功', res)
},
err: (err) => {
wx.hideLoading()
wx.showToast({
title: '释放文件内存失败',
icon: 'none'
})
}
})
}, 2000)
}
});
} else if (type == 'headImg') {
// 获取图片信息进行截取 截取的起止位置也需要计算 然后绘制 x, y, w, h只代表绘制到canvas上的范围
wx.getImageInfo({
src: url,
success: (res) => {
// 根据ui算出来的要展示的比例 width / height
const rat = 1.143
// 图片的宽度
const imgWidth = res.width
// 图片的高度
const imgHeight = res.height
// 剪切图片的宽度, 高度, 剪切X起始位置, 剪切Y起始位置
let cutWidth, cutHeight, cutStartX, cutStartY
if (imgWidth <= imgHeight) {
cutWidth = imgWidth
cutHeight = imgWidth / rat
cutStartX = 0
cutStartY = (imgHeight - cutHeight) / 2
} else {
cutHeight = imgHeight
cutWidth = imgHeight * rat
cutStartX = (imgWidth - cutWidth) / 2
cutStartY = 0
}
ctx.drawImage(res.path, cutStartX, cutStartY, cutWidth, cutHeight, x, y, w, h)
resolve()
},
fail: (err) => {
console.log(err)
wx.hideLoading()
wx.showToast({
title: '获取图片信息失败',
icon: 'none'
})
reject()
return false
}
})
} else {
wx.downloadFile({
url,
success: (res) => {
ctx.drawImage(res.tempFilePath, x, y, w, h)
resolve()
},
fail: (err) => {
wx.hideLoading()
wx.showToast({
title: '下载图片失败,请稍后重试',
icon: 'none'
})
reject()
return false
}
})
}
})
}
function saveImg(path) {
wx.hideLoading()
wx.saveImageToPhotosAlbum({
filePath: path,
success: (res) => {
wx.showToast({
title: '已保存到相册',
icon: 'success',
duration: 2000
})
},
fail: (err) => {
wx.showToast({
title: '保存失败',
icon: 'none'
});
}
})
}
里面有一些buffer文件的转换,绘制图片时比例大小的裁剪,分享图的后台配置愈合与获取等,都是比较常规的业务,就不记了。