小程序如何实现长截图保存功能

2,526 阅读5分钟

知识储备

小程序的架构原理中知道小程序是双线程模式,分为渲染层和逻辑层,那么渲染层用的是webview模式。随着版本的更新升级,随后出现了skyline模式,对webview模式的升级版。

WebView 渲染模式下,一个小程序页面对应一个 WebView 实例,并且每个页面会重复注入一些公共资源。而 Skyline 只有 AppService 线程,且多个 Skyline 页面会运行在同一个渲染引擎实例下,因此页面占用内存能够降低很多,还能做到更细粒度的页面间资源共享(如全局样式、公共代码、缓存资源等)。 —–摘自官网

Skyline 创建了一条渲染线程来负责 Layout, Composite 和 Paint 等渲染任务,并在 AppService 中划出一个独立的上下文,来运行之前 WebView 承担的 JS 逻辑、DOM 树创建等逻辑。这种新的架构相比原有的 WebView 架构,有以下特点:

  • 界面更不容易被逻辑阻塞,进一步减少卡顿
  • 无需为每个页面新建一个 JS 引擎实例(WebView),减少了内存、时间开销
  • 框架可以在页面之间共享更多的资源,进一步减少运行时内存、时间开销
  • 框架的代码之间无需再通过 JSBridge 进行数据交换,减少了大量通信时间开销。

背景

小程序页面具有分享功能,希望能够增加截图长截图功能,将信息详情页面的所有内容,通过分享功能,转发给通讯录中的朋友查看以做推广。

问题

小程序的原理解析中我们知道小程序的两大系统区分便是ios和安卓的区别,长截图功能其实在安卓手机上,系统具有自带长截图功能,然后ios没有,并且小程序无法直接操作dom元素。

项目技术背景

  • taro V3.5.4 ===》 升级到taro.3.6+ 才支持到skyline模式单页面开启
  • React V 17+
  • Webpack V 3.6+

方案预研

方案A:

了解到有一个插件html2Canvas可以实现长截图功能,但是由于无法小程序无法直接操作dom元素,不能直接在小程序中使用。可以通过将需要长截图的页面,写成一个h5页面,然后通过webview页面引入,然后在h5中利用插件做长截图功能。

结果:不可行❌,因为小程序需要长截图页面已经在小程序中存在,再花费时间去写一个h5页面,存在太多浪费的资源和精力,淘汰此方案,如果你是要在h5的项目操作,你可以尝试。

方案B

通过canvas实现将整个页面的内容通过原稿画出来,然后通过canvas.createImage()。画出来之后通过 wx.canvasToTempFilePath 接口,可以将 canvas 上的内容生成图片临时文件,然后进行图片保存功能。

const image = canvas.createImage()
// 图片加载完成回调
image.onload = () => {
    // 将图片绘制到 canvas 上
    ctx.drawImage(image, 0, 0)
}
image.src = 'xxx图片url'

结果:小程序功能页面太多元素,canvas画图太耗费人力和时间,不符合当前需求开发时间内,淘汰❌,如果是单纯的海报,可以值得一试。

方案C

小程序官方文档中提出了一个组件叫做snapshot截图组件,不过需要注意的是,该组件的有一个必要的条件就是必须开启skyline渲染模式才能使用。

经过调研开启skyline模式必须基础库版本>= 3.0.1,微信版本必须8.0.31以上。根据微信提供的数据,大部分的用户的微信版本还是支持skyline模式。

结果:采取snapshot组件实现小程序长截图功能。改动相对较小,同时满足现在开发的周期。

缺点:存在兼容性问题,需要同时兼容两种模式:webview以及skyline模式的样式。

解决思路:

  • 在小程序单页面配置下开启skyline渲染模式

    // page.config.ts
    {
      renderer: 'skyline', // 开启渲染模式为skyline
      componentFramework: 'glass-easel', // 相关框架分为exparser、glass-easel
      rendererOptions: {
        skyline: {
          defaultDisplayBlock: true,
          disableABTest: true,
        }
    }
    

    关于componentFramework的中的glass-easel 介绍。

    Skyline 渲染引擎的相关配置项

    属性类型默认值说明
    defaultDisplayBlockbooleanfalse开启默认 Block 布局
    defaultContentBoxbooleanfalse开启默认 ContentBox 盒模型
    disableABTestbooleanfalse关闭 Skyline AB 实验
  • 在全局配置上开启按需注入,同时设置skyline下节点默认布局模式

    ​
    {
    "lazyCodeLoading": "requiredComponents", // 开启按需注入
        "rendererOptions": {
            "skyline": {
                "defaultDisplayBlock": true // skyline 下节点默认为 flex 布局,可以在此切换为默认 block 布局
            }
        }
    }
    ​
    ​
    
  • 使用snapshot包裹需要放入长图元素。然后通过snapshot.takeSnapshot()对组件树进行截屏。将截屏获取的内容生成的临时文件,可以让用户选择是否下载到本地,即可完成长截图保存功能。

    // demo 举例,基于taro框架
    import { View, Snapshot } from '@tarojs/components';
    ​
    ... 
    // 省略部分代码
    // 将截图内容用snapshot元素包裹
    <Snapshot id='snapshot'>
     <View>{list}</View>
    </Snapshot>
    ​
    // 省略部分代码
    // less文件中,需要对截图组件元素设置离屏渲染。
    // 离屏渲染导出,可将 snapshot 组件移动到屏幕外或设置 width: 100%; position: absolute; transform: scale(0) 即可,但不能设置为 display: none 或 visibility: hidden
    ...
    ​
    
    // 触发生成长截图事件
    Taro.createSelectorQuery()
          .select('#snapshot')
          .node()
          .exec((res) => {
            res?.[0]?.node?.takeSnapshot?.({
              type: 'arraybuffer',
              format: 'png',
              success: (res) => {
                const userPath = Taro.env.USER_DATA_PATH;
                const randomName = Number(Math.random().toString().substr(3, 3) + Date.now()).toString(36);
                const path = `${userPath}/${randomName}.png`;
                // 生成临时图片
                // .....
              },
              fail(error) {
                console.log('fail:', error);
              },
            });
          });
    

开发者调试

  1. 微信开发者工具选择基础版本库>=3.0.1以上

    basic.png

  2. 开启skyline渲染调试,否则在真机上无法调试skyline模式下页面的样式,不会生效。

    skyline.png

QA

开启skyline模式之后,页面的兼容性会出现很多问题,可以查阅文档常见的兼容性问题

  • display的默认属性为inline,暂不支持block
  • iconfont图标无法展示,最新版本已经支持伪元素
  • ……

总结:开启skyline模式出现的问题其实很多,毕竟内部的渲染模式大有不同,所以开发的时候需要调研清楚是否改动比较大,或者难以实现的地方。