微信小程序将页面保存成图片

2 minute read

前言:这玩意是真的坑

开发环境

chameleon框架

原型图

image-20210315093335719

代码实现(各种踩坑)

页面具体怎么画的这里就不多说了,具体讲保存图片这个功能,一看到这个功能就觉得似曾相识,我以前好像做过,立马想到了html2cavas这个插件,开开心心的把这玩意下载下来。这玩意怎么用我在将页面元素转为canvas并导出为PDF文件里面有讲到过,它需要传入一个原生DOM元素,它会把这个DOM绘制成canvas并且返回出来。

这个时候第一个坑来了

当使用document.querySelector的时候,直接报错了。

image-20210315123628498

翻阅文档发现,小程序不支持上面那种方式获取DOM,需要使用小程序提供的api来获取。

image-20210315132433020

但是使用该api获取到的并不是原生DOM,翻阅了很多资料也没找到解决方案,因此我就放弃了使用html2canvas这个插件。

使用chameleon多态协议调用wx.createCanvasContext

我只能老老实实的手动去把这个页面用canvas画出来,微信小程序提供了画布的相关api,但是呢,chameleon框架并不能直接访问这些api,它需要使用一个被称作多态协议的一个东西。

src/component/html2image/下新建index.interface

<script cml-type="interface">
    interface html2ImageInterface {
        saveImage(title: string, code: string, desc: string, img: string): void;
    }
</script>
<script cml-type="wx">
    class Method implements html2ImageInterface {
        /**
           * 
           * @param title 标题
           * @param code 合同号
           * @param desc 描述
           * @param img 二维码
           */
        saveImage(title, code, desc, img) {
            const ctx = wx.createCanvasContext('qrcode');
          	// 设置字体
            ctx.setFontSize(36)
          	// 设置填充色
            ctx.setFillStyle('#F9F9F8')
          	// 绘制矩形
            ctx.fillRect(0, 0, 500, 700)
            ctx.setFillStyle('#000')
          	// 设置文字对齐方式
            ctx.setTextAlign('center')
          	// 绘制文字
            ctx.fillText(title, 250, 100)
            ctx.fillText(code, 250, 180)
          	// 绘制图片 这里的img是base64字符串
          	ctx.drawImage(img, 125, 250, 250, 250)
          	ctx.fillText(desc, 250, 580)
          	// 开始绘制
          	ctx.draw(false)
        }
    }
    export default new Method();
</script>

src/pages/demo.cml页面放入一个canvas标签

<template>
  <button btn-style="background-color:#118EE9" style="margin: 0 auto" type="blue" size="big" text="保存图片" c-bind:onclick="saveImage"></button>
	<canvas style="width: 500px; height: 700px;" canvas-id="qrcode" />
</template>

在保存图片按钮中调用多态协议中的方法,因为canvas只是用来转图片的,所以并不需要把它显示在页面上,页面上还是展示我用view画的样式,所以把它隐藏掉即可。

import html2ImageUtil from '../../../components/QuerySelector/index.interface'

saveImage() {
  html2ImageUtil.saveImage(this.title, this.code, this.description, this.imageSrc)
}

将canvas转为图片并保存

将canvas转为图片需要用到wx.canvasToTempFilePath这个api,还是在index.interface里进行改动。canvas的draw方法里提供一个回调函数,可以在这里面进行转图片

<script cml-type="interface">
    interface html2ImageInterface {
        saveImage(title: string, code: string, desc: string, img: string): void;
    }
</script>
<script cml-type="wx">
    class Method implements html2ImageInterface {
        /**
           * 
           * @param title 标题
           * @param code 合同号
           * @param desc 描述
           * @param img 二维码
           */
        saveImage(title, code, desc, img) {
            //此处省略绘制过程
          	// 开始绘制
          	ctx.draw(false, () => {
              () => {
                wx.canvasToTempFilePath({
                  width: 500,
                  height: 700,
                  destWidth: 500,
                  destHeight: 700,
                  canvasId: 'qrcode',
                  success(res) {
                    // 绘制成功会返回一个res.tempFilePath,就是生成的图片路径,等下需要用到这个
                  })
            })
        }
    }
    export default new Method();
</script>

转成图片并且拿到图片路径之后,我们就可以把图片保存至相册了。要想保存至相册需要用户同意微信小程序访问相册的权限。

<script cml-type="interface">
    interface html2ImageInterface {
        saveImage(title: string, code: string, desc: string, img: string): void;
    }
</script>
<script cml-type="wx">
    class Method implements html2ImageInterface {
        /**
           * 
           * @param title 标题
           * @param code 合同号
           * @param desc 描述
           * @param img 二维码
           */
        saveImage(title, code, desc, img) {
            //此处省略绘制过程
          	// 开始绘制
          	ctx.draw(false, () => {
              () => {
                wx.canvasToTempFilePath({
                  width: 500,
                  height: 700,
                  destWidth: 500,
                  destHeight: 700,
                  canvasId: 'qrcode',
                  success(res) {
                    // 绘制成功会返回一个res.tempFilePath,就是生成的图片路径,等下需要用到这个
                    const { tempFilePath } = res
                    wx.getSetting({
                      success(res2) {
                        // 判断用户是否授权微信小程序访问相册
                        if (!res2.authSetting['scope.writePhotosAlbum']) {
                          // 没有则发起授权弹窗
                          wx.authorize({
                            scope: "scope.writePhotosAlbum",
                            success () {
                              // 授权后将图片保存至相册
                              wx.saveImageToPhotosAlbum({
                                filePath: tempFilePath,
                                success() {
                                  // 保存成功
                                }
                              })
                            }
                          })
                        } else {
                          // 授权后将图片保存至相册
                          wx.saveImageToPhotosAlbum({
                            filePath: tempFilePath,
                            success() {
                              // 保存成功
                            }
                          })
                        }
                      }
                    })
                  })
            })
        }
    }
    export default new Method();
</script>

第二个坑来咯

到这里似乎蛮顺利的,试一下,我的mac可以弹出保存窗口了。

image-20210315141215308

而且保存下来的图片也挺正常的。

image-20210315141251304

可以开开心心的进行真机调试了,当我打开手机,进入页面,保存图片,一切非常顺利,然后当我打开相册我人傻了。

image-20210315141457517

???就邪门,二维码咋没了。难道是我姿势不对?然后我又单手倒立试了一次,依然是没有二维码,看来跟我姿势没关系。作为一名自身的百度工程师,最后还是被我查到了原因。

base64直接在canvas上绘制,在真机上是显示不出来的。。。。。。

这种情况解决办法有两种:

  1. 让后端直接返回图片url,使用wx.getImageInfo将图片的网络地址缓存到本地,它会返回一个本地地址,然后使用这个地址把图片绘制到canvas上面。
  2. 依然使用base64,手动把base64转成图片并存到本地,在拿这个本地地址去绘制到canvas上面。

这里我用的第二种,因为我懒得去麻烦后端了。要在ctx.drawImage(img, 125, 250, 250, 250)之前,处理下这个img。

<script cml-type="interface">
    interface html2ImageInterface {
        saveImage(title: string, code: string, desc: string, img: string): void;
    }
</script>
<script cml-type="wx">
    class Method implements html2ImageInterface {
        /**
           * 
           * @param title 标题
           * @param code 合同号
           * @param desc 描述
           * @param img 二维码
           */
        saveImage(title, code, desc, img) {
          	// 此处省略绘制图片之外的过程
          
          	// 文件管理对象
          	const fs = wx.getFileSystemManager();
          	// 分别获取图片后缀和图片base64内容
            const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(img) || [];
            const times = new Date().getTime();
          	// 自定义本地图片路径 时间戳+图片后缀
            const imgSrc = wx.env.USER_DATA_PATH + '/' + times + '.' + format;
          	// 将Base64字符串转成ArrayBuffer对象
            const buffer = wx.base64ToArrayBuffer(bodyData);
          	// 保存文件
          	fs.writeFile({
                filePath: imgSrc,
                data: buffer,
                encoding: 'binary',
                success() {
                  // 绘制图片 这里不再使用base64,直接传入上面生成的imgSrc
          				ctx.drawImage(imgSrc, 125, 250, 250, 250)
                  
                  // 此处省略将canvas转为图片并保存过程
                })
        }
    }
    export default new Method();
</script>

再次真机调试,一切ok。

image-20210315143549265

总结

微信开发者工具真垃圾!!!