最近有遇到一个需求,需要动态生成海报图片,而小程序中,生成图片必然绕不开canvas,为了开发效率,再让我去造已有的轮子已然是不现实,时间成本也不允许,所以我调研了目前已有的一些轮子,发现还是wxml2canvas更简单易用,今天我就记录一下使用的一些方法和踩过的坑吧
一、官方的方案和wxml2canvas的优劣
有一款针对web端的生成图片或其他格式的解决方案,是使用**html2canvas
,但是它是需要操作dom节点生成节点列表,而小程序是没有dom这一概念的,微信官方有推出一款用于将模板转成canvas最后转成图片的idea
,名为:wxml-to-canvas
,但!本人亲测,非常难以使用。首先:我的页面样式非常复杂,涵盖了动态高度,动态内容变化,各种定位及很多的图片,这就是wxml-to-canvas
所不能实现的,你要说页面是个写死的静态海报,那没问题,我还是非常推荐这个的,因为你就写个静态模板,用于生成canvas**,最后转成图片!
那么针对以上我所说的,还有文字换行、表情文字图片混排、子标题等元素都要一一绘制等等,使用wxml2canvas就是不二选择。实现一个通过wxml节点标记,收集元素从而进行编译转换,仅依赖wxml直出需要绘制的canvas进而快速生成分享图。
二、实现原理
它包含了对块级元素、行内元素、背景色等简单样式的转换,达到:wxml -> canvas -> image 的一次性处理,且不需要重复书写静态代码模板进行编译。
小程序提供了如下特性、可供我们便捷使用:
- measureText 接口能直接测量文本宽度;
- SelectorQuery可以查询到节点对应的computedStyle。
同时小程序也存在一些弊端,比如:
- 通过SelectorQuery只能拿到节点的style,而无法获取文本节点的内容
- canvas在调试时可能置于最顶层,影响效果
所以我们第一步获取元素就面临两个问题:
- 如何获取需要转成canvas的元素
- 如何拿到获取元素的对应属性(样式、节点、内容…)
当获取到待收集的元素后,就可以将元素绘制到指定的canvas上,也就实现了wxml2canvas
三、如何使用
1.可以通过npm构建wxml2canvas:
npm install wxml2canvas
2.引入
import Wxml2Canvas from 'wxml2canvas';
3.使用
html
<canvas canvas-id="share"></canvas>
<view class="share__canvas share__canvas1">
<view class="share__canvas1-text draw_canvas"
data-type="text" data-text="这是一段无边距文字">
这是一段无边距文字
</view>
</view>
如果需要使用到图片,其中data-url是图片在canvas显示的地址,即:
<image class="draw_canvas" data-type="image"
data-url="/static/share/friend_logo.png"
data-delay="1" mode="aspectFit" src="/static/share/friend_logo.png"></image>
其中,还有text标签可以使用,必填属性为data-type,声明此标签的在canvas中展示类型,每个标签都有data-top和data-left的属性,分别代表元素参照与自身位置的偏移值。 需要注意的是,data-text是生成的canvas中显示的文字,可以理解为标签的自定义属性,也可以接受变量等。
写入事件,用于获取id为wxml-canvas的宽高,这里的获取的元素的宽高就是将来生成canvas的父元素,获取之后触发draw函数。
//动态获取画制作元素的宽高
drawCanvas: function () {
const query = wx.createSelectorQuery().in(this);
query.select('#wxml-canvas').fields({
size: true,
scrollOffset: true
}, data => {
let width = data.width;
let height = data.height;
this.width = width;
this.height = height;
setTimeout(() => {
this.draw()
}, 1500);
}).exec();
初始化
/**
* element:需要渲染的canvas节点
* class:查找所有类名为exc-c的节点,并进行加入绘制队列
* limit:限定相对位置
*
**/
wxml2Canvas({
element: "over-canvas",
options: {
class: ".exc-c",
limit: ".limit-r",
},
})
draw() {
let that = this
//创建wxml2canvas对象
let drawImage = new Wxml2Canvas({
element: 'share', // canvas节点的id,
obj: that, // 在组件中使用时,需要传入当前组件的this
width: this.width * 2, // 宽高
height: this.height * 4,
background: '#fff', // 默认背景色
progress(percent) { // 绘制进度
},
finish(url) {
console.log("创建的图片", url);
},
error(res) {
console.log(res);
// uni.hideLoading()
// 画失败的原因
}
}, this);
let data = {
//直接获取wxml数据
list: [{
type: 'wxml',
class: '.long_friend .draw_canvas',
limit: '.long_friend ',
x: 0,
y: 0
} ]
}
//传入数据,画制canvas图片
drawImage.draw(data, this);
},
创建wxml2canvas对象
这一步是为了创建wxml2canvas对象,其中传入的参数有三个,第一个参数为一个对象option。第二个参数为创建图片成功时的回调函数,成功时的回调函数第一个参数为图片的地址。第三个参数为失败时的回调函数,会返回失败原因以及状态码,第四个参数传this,是为了防止“canvasToTempFilePath: fail canvas is empty ”报错,报这个错是因为调取wx.canvasToTempFilePath 接口获取不到canvas,同样的需要传入上下文的this。
其中option的宽高,也是生成图片的宽高,我将其设置为元素的初始宽度的两倍,是因为如果是等比的话,图片会模糊。如果发现将宽高*2还是出现图片糊的情况,可以适当调整倍数。
//创建wxml2canvas对象
let drawImage = new Wxml2Canvas({
element: 'share', // canvas节点的id,
obj: that, // 在组件中使用时,需要传入当前组件的this
width: this.width * 2, // 宽高
height: this.height * 2,
background: '#fff', // 默认背景色
progress(percent) { // 绘制进度
},
finish(url) {
console.log("创建的图片", url);
},
error(res) {
console.log(res);
// uni.hideLoading()
// 画失败的原因
}
}, this);
四、遇到的问题
文字绘制需要添加data-type=“text” data-text=“要绘制的文字”
<text class="time answer_draw_canvas" data-type="text" data-text="要绘制的文字">要绘制的文字</text>
边框不展示
要看一下是否设置了box-sizing: border-box;如果css设置了该属性会导致边框不出现
图片
图片本地的正常引用,如果是链接的一定要保证配置白名单,否则生成会一直加载中
文字加粗
官方文档是支持加粗的,但是我试过各种方法设置加粗都无济于事,估计是有bug。如果有看到这个文章的小伙伴调试出来了文字加粗麻烦留个言,在此,感谢!
边框圆角
目前未有边框圆角设置,所以ui有要求,要么改掉,或者加背景图片
背景色问题
如果背景色是设置在view或者其他官方未支持的标签时背景色是不会生效的,比如官方支持text标签,设置背景色是有效果的,这个要注意
五、总结
功能本身不复杂,逻辑也很简单,无非是怎么把页面结构转成canvas,通过 块级元素
和 行内元素
的拆分可以将目标元素转为指定的 canvas
,从而可以让使用者利用小程序的 wx.canvasToTempFilePath
将canvas对象转为图片最终让用户进行保存。用什么工具去转最方便需要自己根据实际情况去判断。再说一点,微信小程序官方自带组件是很坑的,问题也不少,要注意,再会!