Taro + React 实现自定义海报

目标&最终效果

分享页面 分享导出的图片
|254x564

在微信小程序中, 点击生成海报图片, 能够生成如上图片, 并且保存到本地.

解决方案

心路历程

一开始, 本来是想要使用 html 2 canvas 这个库的.

无论如何, 经过一番查询后得出: 这个库需要获取 DOM 元素, 但是微信小程序中, 我们无法正常的获取 div 之类的 DOM 元素. 因此, 该方案 pass.

随后, 查询到了可以使用 canvas 进行绘制, 但是考虑到页面元素过于复杂, 如果需要一个个的绘制, 并且考虑兼容性的话, 这将是一个特别特别大的工程, 甚至比绘制整个课表都要复杂得多.

最后, 经过了一整晚几个小时的探索, 我找到了一个符合当前需求的东西: Snapshot.

|456x194

配置环境

这个组件是 微信小程序 中, 用来将子节点的渲染结果导出成图片用的, 不过它只支持 Skyline 渲染框架, 所以我们需要先配置一下 skyline 的相关配置.

这里我使用的是 React + Taro 的开发环境. 首先在 src 目录下的 app.config.ts 中, 配置一下 skyline 的默认渲染模式.

1
2
3
4
5
6
7
8
9
10
11
export default defineAppConfig({
// ...
rendererOptions: {
skyline: {
defaultDisplayBlock: true,
disableABTest: true,
sdkVersionBegin: "3.0.1",
sdkVersionEnd: "15.255.255",
},
},
})

这里配置的作用是配置 skyline 的默认样式, 因为 skyline 如果完全默认的话, 是没有一些样式的, 比如 flex 布局中的方向等等.

考虑到后面需要使用到大量的 flex 布局, 干脆在这里直接配置 defaultDisplayBlock 为 true.


考虑到我目前的小程序中, 只有这一个页面使用到了 skyline, 所以不需要全局进行配置 render 选项. 只需要在当前页面进行配置即可.

我这里的是一个 share 页面, 需要用到 Snapshot 组件, 所以在页面文件夹下的 index.config.ts 中, 配置一下 skyline.

1
2
3
4
5
export default definePageConfig({
// ...
renderer: "skyline",
componentFramework: "glass-easel",
});

至此, 就可以使用该组件了.

使用组件

在页面中, 直接使用 Snapshot 组件包裹需要导出为图片的部分即可. 比如下面这样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Index = () => {
return (
<View>
{/*顶部*/}
<CustomNavBar fixed>分享帖子海报</CustomNavBar>
{/* 配置一个总体框架 */}
<View className="share-forum-container">
<Snapshot id="snapshot-container" mode="view">
{/* 需要导出为图片的部分 */}
</Snapshot>
</View>
</View>
);
};

export default Index;

这里的 mode, 不需要设置为 picture, 否则会导致无法正常加载图片资源. 另外这里的 ID 也是必要的, 用来获取其中的 DOM 元素.


随后, 我们可以通过如下代码来进行保存图片的操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const handleSave = () => {
// 判断渲染模式 做一层判断操作
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
if (Taro.getCurrentInstance().page.renderer === "webview") {
Taro.showModal({
title: "警告",
content: "您当前的小程序渲染模式不支持, 请切换到 skyline 渲染模式.",
});
return;
}

// 否则就是支持的了 直接开始保存流程
Taro.createSelectorQuery()
.select("#snapshot-container")
.node()
.exec((res) => {
res[0].node.takeSnapshot({
type: "arraybuffer",
format: "png",
success: async (res2: any) => {
// 生成一个图片路径
const tempFilePath = `${Taro.env.USER_DATA_PATH}/${new Date().getTime()}.png`;

// 写入图片
const fs = Taro.getFileSystemManager();
fs.writeFileSync(tempFilePath, res2.data, "binary");

// 写入图片后, 进行保存操作
await Taro.saveImageToPhotosAlbum({
filePath: tempFilePath,
success: () => {
Taro.showToast({
title: "保存成功",
icon: "success",
});
},
});
},
});
});
};

至此, 效果成功实现, 点击保存就会自动将包裹的元素保存到本地了.

额外记录

报 Warning

启动该页面后, 会发生报 Warning 占满整个屏幕的情况.

不过这是正常情况, 只要效果实现了, 在手机上跑起来问题不大, 就没问题.