小程序

仿微信圈子绘制带小程序码的海报(超详细爬坑指南)

2020-02-27  本文已影响0人  一恋蝶梦

前言:在弄「新城名录」这个小程序时,需要将发布的信息内容生成一张带小程序码的海报,方便分享和转发。海报的形式是参照“微信圈子”里面的样式,折腾不了不少时间,也踩了很多坑,故在此记录下来。

首先看看实现效果


点击“生成海报”,预览生成的海报然后保存至相册

此小程序是基于uni-app开发的,也就是vue那套写法,所以将海报的生成逻辑弄成了单独的组件。
整个实现流程大致如下图:


绘制过程

一、初始化基本尺寸

通过wx.getSystemInfo获得窗口信息。
因为canvas上绘制文字单位是px,所以要通过像素比来计算文字大小。

let _ = this
wx.getSystemInfo ({
    success (res) {
        _.windowInfo.width = _.canvasStyle.width = res.windowWidth
        _.windowInfo.height = res.windowHeight
        _windowInfo.ratio = res.pixelRatio / 2
        // 根据像素比,计算文字的大小
        _.canvasStyle.textDf *= _windowInfo.ratio
        _.canvasStyle.textSm *= _windowInfo.ratio
    }
})

二、获取小程序码

2-1: 生成buffer

获取小程序码看文档就行了没什么可说的:传送门
我是通过云函数获取小程序码的Buffer

// 云函数
app.router('wxacode', async (ctx, next) => {
    try {
        let rs = await cloud.openapi.wxacode.get({
            path: event.path,
            width: 100,
            is_hyaline: true
        })
        ctx.body = rs
     } catch (err) {
        ctx.body = err
     }
}) 

wxacode.createQRCodewxacode.get 两个接口加起来最多可生成10万个小程序码。同一参数的path是一个,不限请求次数。
wxacode.getUnlimited 是无限个小程序码,但path有限制,好像不能带参数还没试过。

2-2: 调用云函数获得buffer并转为base64

wx.cloud.callFunction ({
     ...
}).then(rs => {
    let base64 = wx.arrayBufferToBase64(rs.result.buffer.data || rs.result.buffer)
})

在开发的过程中莫名其妙的小程序码就绘制不出来了,最后发现这里rs.result.buffer对象中,一会有data字段一会没有,别问为什么总之遇到了,最好判断下。

let imgSrc = 'data:image/jpeg;base64,'  + base64

这里转换的是没有base64前缀的,若要显示在 <image> 标签上,需要加上前缀。

2-3:获得本地临时链接

用wx.getFileSystemManager().writeFile方法写入到本地。
在绘制完成之后,通过wx.getFileSystemManager().unlink删除临时文件。

let filePath = `${wx.env.USER_DATA_PATH}/wxacode.jpeg`
wx.getFileSystemManager().writeFile({
    filePath,
    data,
    encoding: 'base64'
    ...
 })

三、计算画布的高度

首先看看海报的布局



由图可知画布的高度=图片区域+文字区域+小程序码区域+边距

3-1:计算绘制图片所占的高度

用canvas绘制图片,首先要将图片下载成功后才能绘制。
在使用wx.downloadFile下载图片时,如果遇到错误:downloadFile:fail url not in domain list
那么就要在小程序管理后台中:开发>开发设置>服务器域名 去设置downloadFile合法域名
如果用到了云存储,合法域名就在 云开发>存储 中找到文件的https的下载地址


云存储的域名

图片下载完成后,通过wx.getImageInfo来获得图片的尺寸,然后根据数量不同采用不同的排版方式。


图片排版方式
在这里获取到图片信息时,就计算好坐标、尺寸暂存起来,等绘制的时候直接使用即可。

3-2:计算绘制文字所占的高度

绘制文字主要的问题是,canvas是没有自动换行的,所以要把文字一个个的取出来,然后计算宽度。
小程序提供了测量文本尺寸信息的接口:CanvasContext.measureText
这个玩意呢不建议用,因为在真机测试时,这个接口的运算速度啊慢得要死,文字一多简直不能玩了。
后面找了个现成的函数来获取文本的宽度。原文链接
同样在这里获取文字宽度信息的同时,将坐标计算好暂存起来,等绘制的时候直接使用即可。
最后是小程序区域的高度,是固定高度100

四、开始绘制

4-1: 获取CanvasContext

首先创建 canvas 的绘图上下文 CanvasContext 对象
注意这里要将this参数带上
在自定义组件下,当前组件实例的this,表示在这个自定义组件下查找拥有 canvas-id 的 canvas ,如果省略则不在任何自定义组件内查找

let ctx = wx.createCanvasContext ('mycanvas', this)

然后就是按顺序绘制图片、文字、小程序码区域

4-2: 导出图片

绘制完成之后就需要用wx.canvasToTempFilePath,将画布的内容导出成指定大小的图片。
官方文档说了:在 draw() 回调里调用该方法才能保证图片导出成功。
理论上应该是如下这样子:

ctx.draw(true, () =>  {
    wx.canvasToTempFilePath({ ... })
})

但是,这个回调它根本不执行呀!
后面查到的是说:绘制速度太快(what ???) 无法进入canvas.draw的回调函数,需要在外层套个setTimeout。

let _ = this
ctx.draw(true, setTimeout(() => {
    wx.canvasToTempFilePath ({
        fileType: 'jpg',
        canvasId: 'mycanvas',
        x: 0,
        y: 0,
        width: _.canvasStyle.width,
        height: _.canvasStyle.height,
        success (res) {
              _.imgSrc = res.tempFilePath
        }
    }, _)
}, 300))

在使用wx.canvasToTempFilePath记得把this传入进去,同时最好指定好画布区域的宽高,不然可能存在图片空白的情况。

4-3: 预览图片

绘制的<canvas>节点是隐藏在屏幕外的,真正用于预览的是<image>节点
如图:


为什么不直接用<canvas>来预览?
因为预览的尺寸和canvas的尺寸不一样,所以就要做缩放,将<canvas>标签用css3 transform:scale(.8, .8) 在真机上是没有作用的!!!
所以只能把wx.canvasToTempFilePath导出的图片路径,放到<image>上来做显示。
但是,
导出来的图片有可能存在留白区域,像就是没绘制完,这里就要如4-2所说,把导出写到draw回调中,同时一定要把延时加上。

五、保存图片

当你满怀欣喜的爬完上面的坑,以为调用一下wx.saveImageToPhotosAlbum接口把图片保存完,就大功告成了码?
不存在的!!!
此接口需要用户授权,才能成功将图片保存,而如果用户不小心点了拒绝授权,那么是不是要手动调用下跳转到授权设置页面。

wx.getSetting({
    success(res) {
        if (!res.authSetting['scope.writePhotosAlbum']) {
            wx.authorize({
                scope: 'scope.writePhotosAlbum',
                ...
                fail () {
                    wx.openSetting(...)
                }
            })
        } 
    }
})

跳转授权?跳转得了屁勒!
翻看wx.openSetting的官方文档,有那么一小撮字告诉你:用户发生点击行为后,才可以跳转打开设置页
所以你还得整个对话框,让用户点一下子。

wx.getSetting({
    success(res) {
        // 进行授权检测,未授权则进行弹层授权
        if (!res.authSetting['scope.writePhotosAlbum']) {
            wx.authorize({
                scope: 'scope.writePhotosAlbum',
                // 拒绝授权时,则进入手机设置页面,可进行授权设置
                fail(err) {
                    wx.showModal({
                        title: '提示',
                        content: '需要您授权才能保存到相册',
                        success: (res) => {
                            if (res.confirm) {
                                wx.openSetting({
                                  ...
                                })
                            }
                        }
                    })
                }
            })
        } 
    }
})

最后附上完整代码地址:https://github.com/yiPian/poster

上一篇 下一篇

猜你喜欢

热点阅读