作者 | 结一
为了吸引更多的用户,设计好一个分享海报还是很有必要的。而小程序要生成一个海报还是有点坑的,下面分享下我们打卡小程序的一些经验。
分享海报的效果图如下:
制作要求:
海报以弹窗形式展现,各种手机型号都可以正常显示
海报的内容由背景图、日期、随机的名言警句、活动的二维码及用户的参加活动的信息
海报保存的图片大小为 iphone 6 的两倍图(750 * 1334)
由于看到的弹窗图片与保存的图片是两种大小,所以一开始看了些网上的其他教程,建议是搞两个 canvas 一个大的一个小的。实际过程中,采用了一个大的 canvas ,让其偏离视窗显示区域(不可见)并生成临时文件,弹窗的图片再使用 img 组件,引入临时文件,设置其高度;而保存的时候则直接下载临时文件。
虽然是实现了,但是后来在优化的过程中,这个方案也重新设计了。下面具体介绍下优化过的方案:
优先使用一个 canvas 绘制二维码;
弹窗的图片即为一个 canvas;
分享的图片为该 canvas 导出的大图片;
为了达到最佳效果,名言警句的换行录入时就处理好。
设计弹窗的图片比例
由于最后海报的图片大小为 iphone 8 的两倍图(750 * 1334),所以这里弹窗的图片也即是 canvas 的大小,设计为对应的尺寸的某个比例。
弹窗图片的高度 = 视窗的高度 - 上下留白 - 按钮的高度 - 图片与按钮的距离弹窗图片的高度 = 100vh - 40rpx * 2 - 50rpx - 15rpx
// iPhone 8 的尺寸标准为 750px * 1334px弹窗图片的宽度 / 弹窗图片的高度 = 750 / 1334弹窗图片的宽度 = (750 / 1334) * 弹窗图片的高度
由于像素只能是整数,所以这样绘制出来的图片可能底部会有1px的空白,所以在设置高度的时候可以再减掉 1px,这不会影响视觉效果。
绘制背景图
如果是网络图片,绘制背景图之前一定要先下载该图片,可通过 wx.getImageInfo
或 wx. downloadFile
下载图片,下载成功后将其塞进临时地址,然后使用 wx canvas 的 drawImage
绘制。注意如果地址是 base64 的话, gif 是不行的。
// 预先下载图片preLoadImg(url, taskId) { url = /^https/.test(url) ? url : `https:${url}`; wx.getImageInfo({ src: url, success(res) { // 把图片存下 app.globalData[`tempImg${taskId}`] = res.path; }, fail(err) { console.log(err); }, }); },
绘制二维码
绘制二维码换了好几个库,每个在安卓下面生成的二维码都会频现失败。查了好些资料,说是安卓绘制的时候要设置个 setTimeout,于是最终选择了weapp-qrcode,修改了其绘制的函数,增加了setTimeout(还真别说,加上二维码绘制就成功了)。
ctx.draw(false, function (e) { setTimeout(() => { // 修改增加的 options.callback && options.callback(e) }, 20);})
另:目前这些绘制小程序二维码的库都是在一个单独的新 canvas 中完成的,只要对源码稍作修改,就可以提供另一个接口,直接在一个现有的 canvas (表示 canvas 中一开始绘制了其他内容) 中绘制。
如果二维码扫不出来,则表示二维码绘制出了问题。但安卓微信 6.7.2 版本本身有个 bug,二维码本身是没有问题,它却不能识别。不过升级下微信版本就好了。
canvas 绘制完毕及下载
具体的绘制调用的都是 api,就不多说了。主要说下绘制完毕如何处理。先异步触发绘制完毕,该事件中将 canvas 显示出来,也即是小图。同时生成n倍分享图,并触发事件,该事件中将大图的缓存文件保存下来。然后到下载的时候使用。
// 绘制完毕处理ctx.draw(false, async () => { // canvas 绘制好了触发 drawDone setTimeout(() => { this.triggerEvent('drawDone', this); }, 100);
// 生成用于分享的图,pixelRatio 为 倍数 const { canvasId, canvasWidth, canvasHeight, pixelRatio } = this.data; const { tempFilePath } = await wxCanvasToTempFilePath( { x: 0, y: 0, width: canvasWidth, height: canvasHeight, destWidth: canvasWidth * pixelRatio, // 导出大小为 canvas 的 pixelRatio 倍 destHeight: canvasHeight * pixelRatio, canvasId, }, this ); // 生成分享图完毕触发 this.triggerEvent('saveDone', tempFilePath);});
// 下载处理// 路径为上一步保存的分享图暂存图wx.saveImageToPhotosAlbum({ filePath: cardTempImgPath, success() { wx.showToast({ title: '保存成功', // 提示的内容, icon: 'success', // 图标, duration: 2000, // 延迟时间, mask: true, // 显示透明蒙层,防止触摸穿透, }); }, fail(err) { console.log(err); wx.showToast({ title: '保存失败', // 提示的内容, icon: 'none', // 图标, duration: 2000, // 延迟时间, mask: true, // 显示透明蒙层,防止触摸穿透, }); }, });
保存图片
先要获取用户是否开启用户授权相册
如果没有权限,则弹窗提示开通权限,如果有权限直接调用
saveImageToPhotosAlbum
接口保存图片如果弹窗提示接受开通权限,则调用
saveImageToPhotosAlbum
接口保存图片如果弹窗提示拒绝则再次弹窗是否去设置开通权限,如果是则进入设置权限
// 先尝试保存trySaveImg() { const _this = this; // 获取用户是否开启用户授权相册 wx.getSetting({ success(res) { // 如果没有则获取授权 if (!res.authSetting['scope.writePhotosAlbum']) { wx.authorize({ scope: 'scope.writePhotosAlbum', success() { _this.saveImg(); }, fail() { // 如果用户拒绝过或没有授权,则再次打开授权窗口 // (ps:微信api又改了现在只能通过button才能打开授权设置,以前通过openSet就可打开,下面有打开授权的button弹窗代码) wx.showModal({ title: '获取权限失败', content: '是否打开设置页,允许小程序保存图片到你的相册', success: () => { wx.openSetting({ success (sRes) { if (sRes.authSetting['scope.writePhotosAlbum']) { setTimeout(() => { _this.saveImg(); }, 200); } }, }); }, }); }, }); } else { // 有则直接保存 _this.saveImg(); } }, });},// 正式保存saveImg() { const { tempImgPath } = this.data;
wx.showLoading({ title: '正在保存中...', // 提示的内容, mask: true, // 显示透明蒙层,防止触摸穿透, });
wx.saveImageToPhotosAlbum({ filePath: tempImgPath, success() { wx.showToast({ title: '保存成功', icon: 'success', duration: 2000, mask: true, }); }, fail() { wx.showToast({ title: '保存失败', icon: 'none', duration: 2000, mask: true, }); }, });},
性能注意
经实践测试整个绘制过程其实还是很快的,但是如果有保存临时文件操作( wx.canvasToTempFilePath
),那么这个操作一般得占一半时间左右。除此之外,有个 measureText api,用来测量文字的长度,这个在实现自动换行的时候用得到,但是比较耗性能。