[微信小程序]小程序 拼图海报制作功能 思路与问题解决

使用小程序生成分享海报图,是很常见的功能了,很多的小程序都有类似的功能。尤其是阅读类、学习类的,总能看到朋友圈有人分享 “我今天背会了**单词,快来一起学习吧”这种。但是这种海报,都是自动排版生成的,用户是无法手动参与的。

为了增强用户参与性,我们模仿一些图片处理App,实现能够自己拼图生成海报的功能。

1. 整体思路

将整个页面分为 绘制区域 与 操作区域。

  1. 选择背景图。
  2. 增加要绘制的图片、文字等元素(以下简称拼图元素)。增加的拼图元素在绘制区域中展示。
  3. 在绘制区域中,触控操作拼图元素,获取操作后的结果(位置、缩放等)。
  4. 在控制区域,对拼图元素进行其它操作,如层叠顺序改变、旋转、缩放等。
  5. 根据每一个拼图元素的位置、缩放等信息,使用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. 结束

至此,整体思路与开发时出现的问题介绍完毕。欢迎各位讨论交流。

你可能感兴趣的