最近在学习微信小程序的开发。本来以为这个基于 JavaScript 的开发环境使用起来应该很简单,实际使用下来发现还是存在不少坑,比如今天要说的生成图片并分享的功能,就耗费了我几天时间。这篇文章是几天折腾的一个总结。
关于这个图片生成的功能从流程上来说并不复杂,主要是把特定的文字和内容绘制成一张图片。我前面写了一篇文章就是讲如何实现这个功能。
通过上面的文章可以发现,从代码层面把 HTML 生成图片非常简单。但问题是微信小程序的 wxml 不支持 SVG 标签,只有 Canvas 可用。这让本身很简单的事情复杂了起来。另外微信小程序的 Canvas 目前也有新旧两套技术,通过网络搜索到的大部分相关信息都是采用的旧方案。旧方案目前虽然还可用,但存在不确定性。如果是之前的老项目用了还无所谓,新项目还用老技术就不太合适了。
微信小程序官方文档对 Canvas 新方案的介绍也比较坑。虽然在旧方案的每个接口方法前都提示了要使用新方案,但对新方案的介绍却是惜字如金。这无疑会让刚接触小程序开发的新人无形中掉入使用旧方案的坑。
我这次的分享图生成功能经历了三个过程:
- 尝试 wxml-to-canvas。
- 使用 Canvas 旧方案。
- 迁移到 Canvas 新方案。
「wxml-to-canvas」 是官方提供的分享图生成工具。我一开始为了图省事,所以准备采用它来实现相关的分享功能。而且它的使用方式和我上面说的利用 SVG + Canvas 生成图片的方式比较类似。但实际尝试后发现它无法直接渲染需要换行的长文本,所以我放弃了。
随后我通过搜索网上的资料,发现使用 Canvas 自己来绘制也不是很复杂,就开始尝试自己来绘制。因为没留意到新旧方案的差异,所以掉进了旧方案的坑里。直到分享功能完成后准备编译上传时才发现小程序控制台上输出的 「canvas 2d 接口支持同层渲染且性能更佳,建议切换使用」 的提示。于是又换成新方案写了一遍。不过还好这两者的差异并不是很大,大部分绘制逻辑稍做修改就能使用。
以上是过程总结,最后来看看用微信小程序的新 Canvas 2d 如何把一段需要换行的长文本生成为分享图。
首先在 wxml 文件中添加 <canvas>
标签:
<canvas id="share-canvas" type="2d"></canvas>
注意上面代码中的 id
和 type="2d"
属性。如果搜索到的小程序生成图片的文章中 <canvas>
标签没有使用这两个属性,那基本可以确定用的是旧 Canvas 方案。
然后编辑 js 文件为如下代码:
// index.js
// 获取应用实例
const app = getApp()
Page({
data: {
// 要渲染的内容
content: '天下事以难而废者十之一,以惰而废者十之九。'
},
onLoad() {
wx.createSelectorQuery().select('#share-canvas').fields({
node: true,
size: true,
}).exec((resource) => {
const canvas = resource[0].node
const context = canvas.getContext('2d')
/**
* 绘制背景
*/
context.fillStyle = '#EFEFEF' // 设置背景色
context.fillRect(0, 0, canvas.width, canvas.height)
/**
* 绘制文字
*/
context.fillStyle = '#000' // 设置文字颜色
context.textBaseline = 'top' // 设置文字绘制的基准线
context.font = '24px serif' // 设置文字大小和字体
// 计算文字长度并生成换行数据
const words = this.data.content.split('') // 把文字分解为单个的字
const pending = 20 // 设置画布边距
const maxWidth = canvas.width - pending*2 // 设置文字绘制区域最大长度
const lines = [] // 保存文字计算后的行数据
var line = '' // 保存计算文字长度单行数据
var lineWidth = 0 // 保存计算文字长度数据
for (let i = 0; i < words.length; i++) {
line += words[i]
// 计算当前计算行的文字长度
lineWidth = context.measureText(line).width
// 拼凑的文字行长度等于或超过画布时换行
if (lineWidth >= maxWidth) {
lines.push(line)
line = ''
} else if (words.length == (i+1)) {
lines.push(line)
}
}
const lineHeight = 34 // 设置行高
// 渲染行文字
lines.forEach((line, i) => {
context.fillText(line, pending, lineHeight*i+pending)
})
// 预览图片并弹出分享菜单
wx.canvasToTempFilePath({
canvas: canvas,
success: (resource) => {
wx.showShareImageMenu({
path: resource.tempFilePath,
complete: () => {
// 完成分享操作后清理画布内容
context.clearRect(0, 0, canvas.width, canvas.height)
}
})
}
})
})
}
})
代码关键位置都有注释,这里就不再解释了,下面是运行效果截图:
最后要吐槽一下,我折腾了几天的小程序上传后审核没通过。审核结果说我的小程序涉及信息咨询,需要申请企业主体后才能通过审核。以我的理解来看,这个「信息咨询」界限十分不明确且范围极广,意味着我后续想要尝试的几个小程序都可能无法以个人身份通过审核,所以微信小程序对我来说也没有了尝试价值。