使用小程序生成分享海报图,是很常见的功能了,很多的小程序都有类似的功能。尤其是阅读类、学习类的,总能看到朋友圈有人分享 “我今天背会了**单词,快来一起学习吧”这种。但是这种海报,都是自动排版生成的,用户是无法手动参与的。
为了增强用户参与性,我们模仿一些图片处理App,实现能够自己拼图生成海报的功能。
1. 整体思路
将整个页面分为 绘制区域 与 操作区域。
- 选择背景图。
- 增加要绘制的图片、文字等元素(以下简称拼图元素)。增加的拼图元素在绘制区域中展示。
- 在绘制区域中,触控操作拼图元素,获取操作后的结果(位置、缩放等)。
- 在控制区域,对拼图元素进行其它操作,如层叠顺序改变、旋转、缩放等。
- 根据每一个拼图元素的位置、缩放等信息,使用canvas进行绘制,并生成图片。
思路确立了,就开始实操。
2. 素材部分
由于小程序有对包大小的限制,所以我们的图片素材是不能直接扔在小程序里的。
我是直接将素材按照一定的命名规则直接传到了云存储服务中,小程序中通过规则进行拼接获取所有素材的地址。
初始开发时,可以先把图片素材放在小程序文件夹里。不影响开发者工具内的开发与测试,只不过无法真机调试与预览。
为了便于操作与计算,所有的图片素材都采用的统一大小的png格式(最好是正方形)。
3. 绘制区域
1. 组件选择
选用小程序组件
与
。
使用上述组件,可以实现拼图元素在绘制区域内的移动、缩放,监听事件还可以回传触控操作后拼图元素的位置。
详细的使用方式请参考文档。
https://developers.weixin.qq....
注意:
允许双指缩放后,出现了bug,比如移动拼图元素时会产生图片放大、移动时产生惯性动画(即使没有设置)等。因此放弃,采用其他方法实现。*
- 通过列表渲染,渲染
时,一定要严谨的给出唯一key作为
wx:key
,不要使用诸如wx:key="index"
。否则,在删除拼图元素、修改拼图元素层叠顺序时,会出现bug。
2. 单位与比例换算
元素本身在页面的高宽、定位可以使用 rpx。
其内部的拼图元素宽高、位置等,我个人建议使用 px 单位。
- 首先,rpx不是绝对单位,是比例单位,最终生成的图片不能使用rpx作为宽高单位,否则大家生成的图片大小不一致,为此拼图元素的相关数据也应该直接使用px为单位,便于后边的换算。
- 其次,
的监听事件返回的定位信息也是以px为单位的。
比例换算就没什么好说的了,拿到绘制区域的真实宽度,知道输出目标宽度,算就完了~
4. 操作区域
1. 文字拼图元素的新增
图片拼图元素的增加没什么特别的地方,比较容易处理,设置好
的宽、高、x、y等属性,再在其内部增加
即可。文字拼图元素的增加相对麻烦一点。
文字拼图元素可以分成 横排文字 和 竖排文字。
抛开字号、字体、初始的x、y、等共性的样式/属性:
- 横排文字只需为
设置足够的宽度就好了,否则汉字会换行。 - 竖排文字需为
设置1倍字号的宽度,好让每一个文字刚好占据一行。也因此,要考虑line-height
。我们需要为竖排文字设置一个合适的line-height
。我这里直接设置为了 1.5倍字号。
2. 缩放与旋转
之前我们提到过,使用
自带的缩放功能,出现了bug,因此这里通过
手动设置缩放倍数。缩放倍数直接反应在
的宽高样式上。通过直接修改宽高样式实现缩放倍数,一个好处就是缩放时不会影响拼图元素的位置(由左上缩放,而不是中心缩放)。
旋转角度也通过
手动设置(0 - 360)。
注意:
我们在中触控滑动拼图元素,实质上是实时修改所选
的
transform
样式(translate
)。因此,拼图元素的旋转(transform: rotate(*deg)
),应该加在的子元素上。
因为比较麻烦,我这里只为图片拼图元素的缩放与旋转。文字拼图元素的pass掉了。
3. 层叠顺序的修改
是采用 绝对定位 的,因此渲染的顺序就能代表层叠顺序。
5. 使用canvas绘制海报图
1. canvas组件
canvas组件的大小,设置成为最终海报图输出大小即可。
canvas组件可以设置绝对定位,放置在屏幕区域外即可。
2. 不同基础库版本,获取上下文对象的方法/绘制方法
1. 在基础库2.9.0以下版本
使用 wx.createCanvasContext()
方法获取上下文对象。
然后使用小程序的 CanvasContext
API进行绘制操作。
详情请参考文档:https://developers.weixin.qq....
注意:
- drawImage方法第一个参数为图片资源的本地地址,如果使用网络图片,需要通过
getImageInfo / downloadFile
获取本地地址。- 使用
wx.canvasToTempFilePath
方法生成图片,需要将其放在draw
函数的回调里,来保证图片成功导出。
2. 基础库2.9及以上(我用的本方法,后边介绍的注意情况中,有些基于此)
在 canvas
组件中设置 type = "2d"
, 通过 SelectorQuery
获取canvas实例,进而获取上下文对象。
上下文对象的相关API与Web原生一致。
query.select('#myCanvas').fields({node: true, size: true}).exec(res => {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
canvas.width = 1000;
canvas.height = 1500;
//其它操作...
})
注意:
- 获取canvas实例后,需要显示的设置画布的宽高。画布宽高最大值为 1365 * 1365。
- drawImage方法在这里与Web原生方法一致,第一个参数需要为HTMLImageElement(这里我们只讨论HTMLImageElement的情况),因此需要先使用
canvas.createImage
方法创建图片对象,设置src,并等待加载完毕。- 使用
canvas.toDataURL
返回一个包含图片展示的 data URI (data:;base64,)。
3. 一些要注意的地方
1. 所有的原始距离数据都要通过比例校对到canvas的尺寸比例上。
2. 竖排文字的绘制
通过逐字绘制的方式实现。
用 y + n * line-height - (line-height - font-size) / 2
的方式确定第n个汉字的的垂直绘制位置 y'。
一定要记住减去 line-height
带来的空白。
3. 绘制文字时,设置字体属性
ctx.font = "字号 字体"
必须采用完整格式,否则会失效。
4. 文字绘制时,垂直位置 y
会多出1倍字号左右的距离
不知道什么原因导致的,反正我这里出现了。
出现了的话,记得补上。
5. 绘制图片的旋转时
ctx.save();
ctx.translate(图片中心点x,y);
ctx.rotate(度数);
//(负的图片宽高的一半)
drawImage(img, -x, -y);
ctx.restore();
ctx.rotate()的度数是 弧度 。
6. 网络图片加载问题
使用 drawImage
方法绘制图片时,需要先使用 canvas.createImage
方法创建图片对象,设置src,并等待加载完毕。
而所有的拼图素材都是网络图片,异步加载,因此会出现 由于加载速度不同导致绘制顺序错乱、 返回dataURI时图片加载未完成导致部分素材绘制未完成 等问题。
因此需要等待所有要用的素材加载完毕之后,再进行绘制。
这里我将图片的加载封装为 Promise
对象,通过 Promise.all()
实现。
addImagePromise(canvas, temp){
return new Promise((resolve, reject) => {
let imgEle = canvas.createImage();
imgEle.src = temp.image;
imgEle.onload = function(){
//!!!很多人在这里都会习惯写 imgEle.onload = null; 但是小程序内会报错
resolve(imgEle);
}
})
}
...
let eleImagePromiseList = [];
for(let i = 0; i < eles.length; i++){
let temp = eles[i];
if(temp.type == 1){
//文字
eleImagePromiseList.push(null);
}
if(temp.type == 2){
//图片
eleImagePromiseList.push(this.addImagePromise(canvas, temp));
}
}
Promise.all(eleImagePromiseList).then(res => {
//加载完毕,开始绘制
})
6. 把返回的 data URI 存入系统相册
通过 小程序的文件管理器 将 data URI 中base64编码的图片部分存储到本地,再保存至相册。
let fs = wx.getFileSystemManager();
let imagePath = wx.env.USER_DATA_PATH + '/myPost.png';
let imageSrc = this.data.drawImageSrc.replace(/^data:image\/\w+;base64,/, '');
fs.writeFile({
filePath: imagePath,
data: imageSrc,
encoding: "base64",
success(res){
wx.saveImageToPhotosAlbum({
filePath: imagePath,
success(res) {
//...
}
})
}
})
7. 真机调试问题
在 canvas
组件中设置 type = "2d"
的方式进行canvas绘制时,进行真机调试时,会报错,报错来源于 通过createSelectorQuery
获取canvas实例。
可以直接通过 预览 查看效果。如果确实需要调试,请更新开发工具,使用 真机调试 2.0 进行。
相关资料:
https://developers.weixin.qq....
https://developers.weixin.qq....
8. 结束
至此,整体思路与开发时出现的问题介绍完毕。欢迎各位讨论交流。