小程序生成分享图片
DevTools上的动图效果
Canvas结构组成
从上面截图可以看出,canvas的主体组成部分包括:
- 背景图
- 微信头像和昵称信息
- 小程序码(小程序码会携带分享者的信息)
需要注意的问题
通过小程序提供的画布,我们能很容易的完成上面的整个海报的绘制。但是,canvas的绘制都是基于像素(px)级别的,但是在小程序中,我们的预览图和最终生成的图,都需要根据机型进行适配。
代码实现
-
底部弹出的模态框
模态框的实现见下面的XML内容,可以看出具体组成如下:- canvas外面套了一个modals,主要目的是让预览图看起来在DOM的最上面
- shareCanvas就是我们要绘制所有内容的画布
- modals-cancel-black是一层半透明的蒙层,尽量将页面的其它元素遮盖到它的下面
- 其次就是一个关闭按钮和相应的文字说明
图片已保存到相册,可分享给好友
.modals {
position: fixed;
z-index: 999;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.modals-cancel-black {
position: absolute;
z-index: 1000;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .8);
}
- 绘制canvas
从动图可以发现,canvas的绘制是从点击底部模态框的“生成分享图”就开始了
drawShareCanvas: function () {
// 1.计算相对比例rpx
// 2.根据rpx绘制背景图片
// 3.根据rpx绘制微信用户信息部分
// 4.根据rpx绘制小程序码部分
}
上述的1-4的步骤之间,可能存在具体的先后关系,可以根据自己的情况来定制先后顺序
- rpx计算方法
wx.getSystemInfo的返回值中可以得到移动端屏幕的具体宽度,小程序WXSS是以rpx作为衡量单位,我们的canvas宽高都是以rpx计算的宽高。但是canvas绘制时以px为单位绘制。那么我们需要计算当前设备相对于375px的倍数。
后续的所有关于坐标、长宽或者距离都需要乘以这个rpx倍数,才能达到适配所有倍数。
var rpx = 1
wx.getSystemInfo({
success (res) {
rpx = res.windowWidth / 375
// ...
}
})
- 绘制背景
因为背景图稍微比较大,在绘制时,通过ctx.scale()方法将画布整体都缩放了0.5倍
wx.showLoading({
title: '绘制背景图'
})
wx.getImageInfo({
src: 'http://lc-adna4hf0.cn-n1.lcfile.com/33111f0ba3409c8fdef9.png',
success (res) {
// 隐藏背景图loading
wx.hideLoading()
const ctx = wx.createCanvasContext('shareCanvas')
// 绘制背景图
ctx.scale(0.5, 0.5)
ctx.drawImage(res.path, 0, 0, 680 * rpx, 1000 * rpx)
ctx.stroke()
// 绘制其它
},
fail () {
wx.showToast({
title: '背景图获取失败',
icon: 'none'
})
}
})
- 微信用户信息
微信个人图像需要绘制成圆形的,可以用CanvasContext.arc(...)来绘制一个圆弧实现。圆弧会有默认的黑色边框。清除边框使用ctx.setLineWidth(0)。如果需要自定义边框颜色,可以使用ctx.setStrokeStyle('red')来设定。
未指定ctx.clip()时,图片不会被裁剪,看到的图片还是正方形。通过不指定裁剪的方式(注释ctx.clip),可以来精确确定图像的开始点的位置。图像的开始绘制点位置为:圆心横坐标 - 圆弧的半径 = point - radius
let userinfo = wx.getStorageSync('userinfo')
if (userinfo) {
wx.showLoading({
title: '绘制用户信息',
})
// 绘制个人信息部分
wx.getImageInfo({
src: userinfo.avatarUrl,
success (avatar) {
// 隐藏用户信息loading
wx.hideLoading()
let radius = 50 * rpx
let point = 70 * rpx
ctx.scale(1, 1)
ctx.save()
ctx.beginPath()
ctx.setLineWidth(0)
ctx.arc(point, point, radius, 0, 2 * Math.PI)
ctx.setLineWidth(0)
ctx.closePath()
ctx.save()
ctx.clip()
ctx.drawImage(avatar.path, (point - radius), (point - radius), 100 * rpx, 100 * rpx)
ctx.restore()
},
fail (err) {
wx.showToast({
title: '获取用户头像失败',
icon: 'none'
})
}
})
// 昵称
ctx.setFontSize(32 * rpx)
ctx.setFillStyle('white')
ctx.fillText(userinfo.nickName, 140 * rpx, 60 * rpx)
ctx.stroke()
// 称谓
ctx.setFontSize(24 * rpx)
ctx.setFillStyle('#FEFEFE')
ctx.fillText('尚德学习大使', 140 * rpx, 100 * rpx)
ctx.stroke()
}
- 绘制小程序码
小程序码因为会携带分享着信息,需要通过调用后台接口来动态生成,所以是异步的。可以在生成期间loading给用户返回进度。得到小程序码的url地址后,就可以根据前面的绘制方法,找到小程序码的位置绘制即可。 - 保存到系统相册
所有绘制步骤完成后,此时还剩两个事情是待做的。一、保存canvas画布内容到系统相册;二、显示预览图。
根据整张图的制作顺序,小程序码是最后一步完成的,所以小程序码绘制到canvas后,算是所有绘制动作完成,需要执行
ctx.draw()
// 下一步?=> 保存图片到相册
saveAlbum()
// 显示预览图
that.setData({
isCanvasHidden: false
})
按照上面的方式,我们执行saveAlbum后发现,在开发工具上,每次都能正常的弹出保存图片。但是在真机上测试时,偶尔会发现预览图已经出来了,但是到系统相册查看,却发现没有生成想要的图片。
通过查看小程序开发文档才知道,wx.canvasToTempFilePath方法需要在ctx.draw的回调中执行(文档地址)。所以,实际得这么干,毕竟人家定义接口的是大爷(嘻嘻)。
ctx.stroke()
ctx.draw(false, () => {
that.saveAlbum()
})
// 这个段也可以放第四行之后
that.setData({
isCanvasHidden: false
})
保存到相册的相关代码
saveAlbum() {
var that = this
wx.canvasToTempFilePath({
x: 0,
y: 0,
canvasId: 'shareCanvas',
success: function (res) {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(saveRes) {
wx.showToast({
title: '保存成功',
icon: 'success'
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
that.hideCanvas()
}
})
}
})
}
手机端效果展示
不支持mp4预览,转换成gif太大了。
WeChat_20181031180605.mp4
参考文档
小程序Canvas API: https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.html
本篇文章由一文多发平台ArtiPub自动发布