业务场景
二轮充电业务中,用户充电完成后在订单详情页展示订单相关信息,用户点击分享按钮唤起微信小程序分享菜单,将生成的图片海报分享给微信好友或者下载到本地,好友可通过扫描海报中的二维码加群领取优惠。
使用场景及功能:微信小程序 生成海报图片 分享好友 下载图片
使用技术:Taro vue vant canvas
实现效果图
重点步骤拆分
1、封装一个海报分享组件 poster-share.vue
2、用canvas画图,将背景图、费用、二维码等信息绘制在一张图上,其中费用、二维码是动态获取的
3、生成一张本地缓存图片
4、唤起微信分享功能,实现分享和下载功能
重点步骤有了,那么就开干吧!
核心代码实现
1、模版部分
需要一个画布dom用来绘制图片,一个用来存放生成图片的dom
问:canvasId为什么需要动态生成呢?
答:避免一个页面中使用多个组件引起的canvasId重复问题
1<template> 2 <div class="poster-share__content"> 3 <!-- canvas生成的海报图片 --> 4 <img 5 v-if="posterImg" 6 class="poster-share__content--img" 7 mode="aspectFit" 8 :src="posterImg" 9 > 10 <!-- 分享海报canvas绘制部分 --> 11 <canvas 12 class="poster-share__content--cvs" 13 :canvas-id="canvasId" 14 ></canvas> 15 </div> 16</template> 17
2、样式部分
该业务场景下,不能让用户看到画布,但是设置canvas的display为none将不能进行绘制,会报如下错误,导致绘制失败。
实现方式:采用定位的方式,将canvas定位到可视区域外,具体代码如下。
1.poster-share__content { 2 position: absolute; 3 right: -9999px; 4 top: -9999px; 5 width: 560px; 6 height: 852px; 7 opacity: 0; 8 z-index: -1; 9 10 &--img { 11 width: 100%; 12 height: 100%; 13 } 14 15 &--cvs { 16 width: 100%; 17 height: 100%; 18 } 19} 20 21 22
3、核心js部分
开始写核心实现啦~
父组件传参控制子组件是否开始绘制,子组件绘制完成后通知父组件改变状态。
1 name: 'CpPosterShare', 2 model: { 3 prop: 'value', 4 event: 'update:value', 5 }, 6 props: { 7 value: { 8 type: Boolean, 9 default: false, 10 }, 11 config: { 12 type: Object, 13 default: () => ({}), 14 }, 15 }, 16 data () { 17 return { 18 isDraw: false, // 是否开始绘制海报 19 posterImg: '', // 生成的海报图片地址 20 canvasId: `canvasId${ Math.random() }`, 21 screenWidth: null, // 屏幕宽度 22 } 23 }, 24 watch: { 25 value: { 26 handler (val) { 27 this.isDraw = val 28 }, 29 immediate: true, 30 }, 31 isDraw (val) { 32 this.$emit('update:value', val) 33 if (val) { 34 this.init() 35 } 36 }, 37 }, 38
首先,我们做的是一个小程序,将图片放在小程序源码中会加大包的体积,需要从网络上下载图片,因此需要封装一个公共的方法来获取图片的信息。Taro提供getImageInfo方法返回图片的原始宽高、本地路径等信息。
1// 加载图片 2loadImg (src) { 3 return newPromise((resolve, reject) => { 4 Taro.getImageInfo({ 5 src, 6 }).then((res) => { 7 resolve({ ...res }) 8 }).catch((err) => { 9 reject(err) 10 }) 11 }) 12} 13 14 15
该业务场景中涉及绘制多张图片,包括背景图片和二维码图片,需要将多张图片都load完成后才能开始绘制。
1const promiseParams = [this.loadImg(BgImage), this.loadImg(QRcode)] 2const promiseAll = Promise.all(promiseParams.map((item) =>item.catch(() =>null))) 3 4promiseAll.then((res) => { 5 this.draw(res) 6}).catch((err) => { 7 console.log(err) 8}) 9
开始绘制啦~
创建canvas绘图上下文CanvasContext对象,调用Taro提供的方法Taro.createCanvasContext(canvasId)绘制背景图、绘制价格、绘制二维码,这里就不一一赘述了。全部绘制完成后,将画布中的内容导出生成图片,Taro提供了canvasToTempFilePath方法,需要在draw()回调中调用才能保证图片导出成功,返回生成图片的临时路径。
1ctx.draw(false, () => { 2 Taro.canvasToTempFilePath({ 3 canvasId:this.canvasId, 4 }).then((res) => { 5 this.posterImg = res.tempFilePath 6 7 // 唤起分享菜单 8 this.showShareImageMenu() 9 }).catch((err) => { 10 console.log('海报生成失败', err) 11 Taro.showToast({ 12 title: '海报生成失败', 13 icon: 'error', 14 }) 15 }).finally(() => { 16 Taro.hideLoading() 17 this.isDraw = false 18 }) 19}) 20
本地图片生成成功后,唤起微信提供的分享菜单弹窗,可以将图片发送给朋友、收藏、保存到相册。Taro提供showShareImageMenu方法唤起分享菜单弹窗,入参为本地图片路径。
1showShareImageMenu () { 2 if (Taro.showShareImageMenu) { 3 Taro.showShareImageMenu({ 4 path:this.posterImg, 5 }).then().catch((err) => { 6 console.log(err) 7 const { errMsg } = err 8 // 取消操作 errMsg === 'showShareImageMenu:fail cancel' 9 // 拒绝授权 errMsg: "showShareImageMenu:fail auth deny" 10 if (errMsg === 'showShareImageMenu:fail auth deny') { 11 authorize({ 12 scope:'writePhotosAlbum', 13 showModal:true, 14 authName:'保存图片到相册', 15 success: () => { 16 this.downloadImg() 17 }, 18 }) 19 } 20 }).finally(() => { 21 this.isDraw = false 22 }) 23 } else { 24 Taro.showToast({ 25 title:'小程序版本不支持该功能', 26 icon:'error', 27 }) 28 } 29} 30 31 32
用户点击发送给朋友,会调起微信对话框,将生成的海报图片粘贴分享给朋友;
点击收藏,会将海报图片添加到收藏列表中,方便下次查看;
点击保存到相册,会唤起保存图片授权弹窗,用户点击允许,会将海报图片保存在本地相册中。
如果用户在保存图片授权弹窗中第一次点击拒绝,之后再次点击分享下载时,需要有授权提示弹窗,提示用户是否打开设置去授权,具体展示如下。
Taro提供Taro.openSetting方法调起小程序设置页面,用户开启“添加到相册”授权后成功后,调用Taro提供的下载图片的方法Taro.saveImageToPhotosAlbum将图片下载到本地。
| 授权提示弹窗 | 设置页面 | 下载提示 |
其中判断用户是否开启授权的方法具体实现如下:
1/** 2 * 权限获取流程 3 * @param scope 权限英文名称 4 * @param success 授权成功的回调 5 * @param fail 授权失败的回调 6 * @param showModal 授权失败是否展示对话框提示 7 * @param authName 授权失败是否展示对话框提示展示的授权名称 8 * // 例子:开启用户的相册权限 9 authorize({ 10 scope: 'writePhotosAlbum', 11 showModal: true, 12 authName: '保存图片到相册', 13 success () { 14 console.log('授权成功') 15 }, 16 }) 17*/ 18export async function authorize (options) { 19 const { 20 scope, success, fail, showModal = false, authName = '', 21 } = options 22 try { 23 const scopeName = `scope.${ scope }` 24 const auth = await Taro.getSetting() 25 if (!auth.authSetting[scopeName]) { 26 Taro.authorize({ scope: scopeName }).then((res) => { 27 if (res.errMsg === 'authorize:ok' && success) success() 28 }, () => { 29 if (showModal && authName) { 30 Taro.showModal({ 31 title: '授权提示', 32 content: `您拒绝了${ authName }权限,是否打开设置去授权?`, 33 }).then((res) => { 34 if (res.confirm) { 35 Taro.openSetting().then((res2) => { 36 if (res2.authSetting[scopeName] && success) { 37 success() 38 } else if (fail) { 39 fail() 40 } 41 }) 42 } else { 43 fail && fail() 44 } 45 }).catch((res) => { 46 fail && fail(res) 47 }) 48 } else { 49 fail && fail() 50 } 51 }) 52 } else { 53 success && success() 54 } 55 } catch (err) { 56 fail && fail(err) 57 } 58} 59
至此,具体实现完结撒花~ 可以将组件用到页面中了
组件引用
1<poster-share 2 v-model="draw" // 是否开始绘制海报海报 3 config="config" // 海报配置信息 4/> 5 6 7
问题记录
在开发过程中遇到了一些问题,记录一下
现象:点击分享,生成canvas图片。开发者工具上每次都正常,ios机每次都正常,部分安卓机每次都正常,部分安卓机,点击分享之后取消,操作多次,有几次会生成图片失败
报错信息:"errMsg": "canvasToTempFilePath:fail :create bitmap failed"
错误定位解决:canvas需要一直显示,不能有display:none的情况
作者:京东零售 张梦雨
内容来源:京东云开发者社区
