小程序必备技巧(8)—自定义小程序分享卡片(动态绘制图片内容)

9,076 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

有一天接到了这样一个需求:分享列表数据,根据不同数据生成对应的卡片,要求卡片上要展示出此数据的标题、截止时间以及封面图片,如图:

经过翻阅官方文档发现:

  • 分享出来的小程序卡片只能设置标题、没有副标题这一说(所以截止时间这个就不能以文字的方式设置上去了)
  • 分享出来的小程序卡片只能设置一个固定的图片url 所以我们只能将截止时间与封面放到一张图片上才行,所以便有了以下基本思路:

一、分享功能的基本实现

首先,我们先来看一下分享功能的基本实现方式:

官方为我们提供了两种实现分享功能的方式

  • 通过点击右上角菜单中的分享实现
  • 通过点击自定义分享按钮实现

1-1、通过点击右上角菜单中的分享实现

为页面添加onShareAppMessage方法,即可开启该页面的转发功能,要注意的是此方法要返回一个对象,来自定义:

字段说明默认值
title转发标题当前小程序名称
path转发路径当前页面path,必须是以/开头的完整路径
imageUrl自定义图片路径,可以是本地文件路径、代码包文件路径、网络图片路径。支持PNG及JPG。显示图片长宽为5:4使用默认截图(分享的内容所属页面的截图

详情参考官方文档:onShareAppMessage

在添加onShareAppMessage之前,我们可以从模拟器上观察到,页面的分享操作是禁用的:

代码示例

	onShareAppMessage() {
		return {
			title: '我是标题',
			path: '/pages/share/index',
			imageUrl: './img/aaa.jpg',
		}
	},

当在页面中添加上onShareAppMessage方法后,再次点击右上角的“...”,可在模拟器中观察到,页面的分享操作变成了启用状态,此时便可以进行分享啦。分享卡片内容会以方法中返回的对象中的内容进行设置。

1-2、通过自定义分享按钮实现分享

具体步骤如下:

  • 首先我们要添加button组件,并为button组件设置open-type="share"属性
  • 然后我们再在页面中添加onShareAppMessage方法

此时点击按钮时便可触发页面的onShareAppMessage方法,同样,要在onShareAppMessage方法中返回一个对象来配置分享出去的卡片。

下面我们来看一下常见的场景案例,以该需求为背景:

案例

现有一个“分享至微信”的按钮,点击该按钮发起分享:

由于该按钮由两部分组成,图标与文字,所以我们可以将图标与文字使用button包裹起来(这样点击图标区域或文字区域均可发起分享)

<button class="action-item" open-type='share'>
  <image class="action-icon" src="./img/icon_wechat.png" style="position:relative; bottom: -20rpx;" />
  <text class="text">分享到微信</text>
</button>

我们可以给button与其中内容添加类名进行样式调整:

.action-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0;
  background: none;
  padding: 0;
  font-size: 28rpx;
}

.action-item .action-icon {
  width: 104rpx;
  height: 106rpx;
  margin-bottom: 24rpx;
}

!我们使用margin: 0; padding: 0; background: none;来去除button组件的默认样式

当我们点击按钮区域时便可触发onShareAppMessage方法

需要注意的是:

由于添加onShareAppMessage后也会启用菜单上的分享页面操作,如果只想让用户点击按钮触发分享而不启用页面菜单中的页面分享,可以在页面的onLoad或onShow方法中添加:wx.hideShareMenu() 即可

二、动态绘制分享卡片中图片内容

通过以上需求背景我们了解到,不同数据的截止时间与封面都是不一样的,所以我们要动态设置分享卡片的图片内容,由于截止时间也要显示在卡片上,但是除了title标题可以设置文字外,没有其余可以设置文字的属性,所以截止时间需要通过canvas绘制上去,那么我们可以考虑将封面图作为canvas区域的底图一同绘制上去,最后将canvas生成临时文件,将此临时文件的路径作为imageUrl就可以啦!

(需要注意的是!我们一定一定要在点击分享按钮触发onShareAppMessage方法之前将图片绘制生成好!否则在onShareAppMessage方法中将获取不到imageUrl,导致分享卡片的图片区域显示空白,所以要按照实际业务场景考虑好要在哪个时机去绘制图片~)

2-1、准备画布

<canvas wx:if="{{canvasShow}}" canvas-id="endtime" style="width: {{canvasWidth}}px;height: {{canvasHeight}}px;position: fixed; right: -999999999rpx;"></canvas>
  • canvasShow用来控制canvas的显示与移除(在绘制前要将canvasShow置为true,不然绘制不上去,在绘制完毕后再将其移除)
  • 要为canvas设置canvas-id,将来在获取canvas示例中使用
  • canvasWidth与canvasHeight最好为5:4(官方要求,因为分享卡片的图片区域长宽比为5:4)
  • 设置position: fixed; right: -999999999rpx;是为了避免在canvas绘制期间,页面会看到canvas内容(我们是不需要页面上展示出来的,只需要将绘制好的canvas变为图片然后设置到分享卡片上~)
  • canvasWidth我们可以设置为设备的screenWidth,canvasHeight我们可以设置为screenWidht * 0.8,这样我们便保证了canvas的长宽为5:4

我们可以在app.js的onLaunch方法中,通过wx.getSystemInfo获取到systemInfo并设置到app的globalData中:

App({
  globalData: {
    systemInfo: null,
  },
  onLaunch: function() {
    wx.getSystemInfo({
        success: (res) => {
            this.globalData.systemInfo = res
        }
    })
  }
})

然后在页面中设置画布的宽高:

onLoad: function () {
    this.setData({
        canvasWidth: getApp().globalData.systemInfo.screenWidth,
        canvasHeight: getApp().globalData.systemInfo.screenWidth * 0.8,
    })
},

2-2、在点击分享按钮前绘制图片

2-2-1、使用数据封面绘制底图

绘制图片到画布上要使用:

CanvasContext.drawImage(string imageResource, number sx, number sy, number sWidth, number sHeight, number dx, number dy, number dWidth, number dHeight)

  • imageResource:所要绘制的图片资源(网络图片要通过 getImageInfo / downloadFile 先下载)
  • sx:需要绘制到画布中的,imageResource的矩形(裁剪)选择框的左上角 x 坐标
  • sy:需要绘制到画布中的,imageResource的矩形(裁剪)选择框的左上角 y 坐标
  • sWidth:需要绘制到画布中的,imageResource的矩形(裁剪)选择框的宽度
  • sHeight:需要绘制到画布中的,imageResource的矩形(裁剪)选择框的高度

(详情请参考官方文档相关链接:drawImage

由于我们数据的封面都是存于服务器上的,所以要通过getImageInfo获取图片资源再绘制:

!!需要注意的是,图片资源要以https开头,否则获取不到图片,我们可通过replace将http:// 变为 https:// (在调式模式下可以,但是在预览模式下就会发现获取不到图片了)

createImage() {
    // 显示canvas画布
    this.setData({ canvasShow: true })
    // 获取canvas实例(此方法废弃了,但新方式暂时没研究出来如何正确使用,欢迎大家补充~)
    let canvasBox = wx.createCanvasContext('endtime')
    // 绘制分享底图
    wx.getImageInfo({
        src: this.data.operateItem.coverUrl
        ? this.data.operateItem.coverUrl.replace('http://', 'https://')
        : '',
        success: (res) => {
            canvasBox.drawImage(res.path, 90, 40, 180, 234)
        },
        complete: () => {
            // 绘制截止时间...
            this.drawCanvas(canvasBox)
        },
    })
},
  • drawImage中的图片x,y与宽高根据实际业务场景来,此处业务场景两边需要留白,所以根据绘制的图片大小设置了x y 为90 40

2-2-2、绘制截止时间(实际业务场景中要展示的文字)

由于此业务场景中不管有无获取到封面图并绘制上去,也要绘制上截止时间,所以考虑在getImageInfo方法的complete中去编写(complete无论该方法成功或失败都会触发)

drawCanvas(canvasBox) {
    let _this = this
    canvasBox.setFontSize(24)
    canvasBox.setFillStyle('#999')
    canvasBox.setTextAlign('left')
    canvasBox.fillText(
            `请在${this.data.operateItem.endTime}前完成`,
            0,
            20,
            _this.data.canvasWidth
    )
    canvasBox.draw()
    setTimeout(() => {
        wx.canvasToTempFilePath({
            canvasId: 'endtime',
            success: (res) => {
                wx.saveFile({
                    tempFilePath: res.tempFilePath,
                    success: (res) => {
                        _this.setData({
                            imageUrl: res.savedFilePath,
                        })
                    },
                })
            },
            complete: () => {
                // 删除canvas
                this.setData({ canvasShow: false })
            },
        })
    })
},
  • fillText(string text, number x, number y, number maxWidth) 中的参数分别表示:所要绘制的文本、x坐标、y坐标、最大宽度
  • 调取draw方法才是将文本真正去绘制上去
  • wx.canvasToTempFilePath:把当前画布指定区域的内容导出生成指定大小的图片,需要传入canvas-id
  • wx.saveFile:将临时文件保存至本地,保存成功后,将保存的地址信息保存下来,方便后续设置给imageUrl
  • 在完成操作后,移除canvas画布
  • 需要注意的是,要保证draw完毕后,才能成功将canvas上绘制的内容生成图片(所以在生成图片时加了setTimeout)

至此我们的前置工作就做好啦,我们下面只需在点击按钮时触发的onShareAppMessage方法中设置上需要设置的卡片内容即可!

2-3、通过onShareAppMessage给分享卡片设置内容

onShareAppMessage() {
    return {
        title: 'xxxx',
        path: `/pages/share/index?id=${this.data.operateItem.id}`,
        imageUrl: this.data.imageUrl,
    }
},

需要注意的是,模拟器下是看不到分享卡片上生成的图片的,需要在真机上测试! 至此,我们便可根据不同数据来生成属于该数据的分享卡片了~