本文将总结在此次「Animal MBTI 测试活动」中遇到的一些问题和技术点,主要还包括 Lottie 的实战应用以及如何与设计小伙伴沟通尽可能简化整个工作流程
MBTI测试预览

Lottie 使用

因为在我们的具体实现中,会涉及多语言的替换,而目前网上能搜索到的 Lottie 结合多语言实现的资料并不算详细,所以也分享一下我此次的处理。
另外这方面我也找了一篇比较好的文章: 剖析 lottie-web 动画实现原理 - 网易云音乐技术团队

准备工作

  1. AE 导出设置
    首先第一步,从 AE 导出 lottie 时都记得去掉「Glyphs」这个选项,并且指定具体的 font family (这里最好统一字体),记得把这件事告诉你的设计师小伙伴
    image

  2. 字体包
    这里还需要设计师提供对应的字体包,注意控制字体包大小,一般会给到otf格式,可以转换成woff2 体积更小的格式。另外 SF Pro 字体已存放在当前 hellotalk-application 仓库中,可以直接使用。

  3. 约束命名
    就像多语言的插入变量一样,需要设计师将有多语言的文本(注意不是图层名)替换为我们约定好的命名,目的是为了方便我们能去查找到对应的文本做调整。例如以下场景:
    我这里给出的命名就是 ${title} 、${value1}、${value2} (仅供参考,随意发挥)
    此时设计小伙伴那边给到的JSON中,就会包含这个命名,结构如下:
    image

这里可能需要知道的一些字段:

  • sz - size 文本框的宽高
  • ps - position 文本框位置
  • s - font size 字体大小
  • f - font 字体名称
  • t - text 文本内容 (与设计约束的key)
  • fc - font color 字体颜色
  • lh - line height 行高
  • ls - letter spacing 字间距
  • j - justification 对齐方式 (0: left, 1: right, 2: center, 3: justify)
  1. RTL 字体问题
    因为当前 Lottie-web 这个库并不支持 RTL 字体,比如阿拉伯语。并且我们也无法直接通过 CSS 控制方向或顺序(本地化同事说会有语序问题),最终无奈只能让设计渲染一个阿语版本的JSON。
    另外我还在 github 上提了 issue,不过这个仓库似乎已经没有人维护了。如果后续有好的解决方案可以补充在这里。
  2. 关键帧
    这里如果需要关键帧做交互,也需要设计师告诉我们帧数或秒数~
    因为如果是秒数我们也可以自己换算:秒数 * 每秒帧数
    至此,在确认动画没有问题之后感谢一下设计师,后续就是前端的工作量。

数据结构

简单说一下我这里定义的类型,title 和 text 这种就是多语言 key,而 segment 是播放的起始帧和结束帧,因为我这里涉及两个选项,两个选项点击后也应该播放对应的片段,layerId 是自定义的图层 id 。
image

处理 JSON

这里我将资源上传到oss了,因为我这里有有五个动画,所以需要遍历。这里只需要关注最内层循环,而这里的 layers 对应 AE 里的每一个图层,也需要遍历来找到我们与设计师约束好的命名,取出layer.t.d.k.[0].s.t 当做 key 来保存,接下来就可以开始操作了。
image

  1. 替换多语言
    这里定义的 getMultilingualism 如下,主要就是将约束好的 key 找出来,并替换为对应的多语言。
    image

  2. 处理字体
    首先看一下 JSON 这个位置的字体,这里就是设计师在 AE 中使用的字体,要到字体包后用 @font-face 引入一下,然后也命名为这个 fFamily 就可以了。
    image

并在 Lottie 渲染之前加载字体,避免 Lottie 渲染时字体还没加载完成的情况:

1
await document.fonts.load("1em SF Pro");

因为多语言换上去后很容易产生文本溢出的现象,所以我们需要对各个语种做适配。所以我这里写了一个 getReduction ,用来计算应该减去多少字号,因为这里减去字号后还需要减去对应的行高,所以计算差值比较合适。(另外这里一定要找到对应的文本图层做处理,如果直接遍历处理,在 lottie 渲染时会引起死循环,原因不明。)

image

这块因为 JSON 的字体不统一,所以我这里自己处理了一下(设计师的电脑有点带不动 AE ,少做打扰)

1
2
3
4
5
6
// unify font
if (layer?.t?.d?.k?.[0]?.s) {
layer.t.d.k[0].s.f =
locale.value === "Thai" ? "Arial" : "SFPro-Regular";
}

最后因为我的这个字体包不支持泰语,所以我用了一个系统字体来代替。

渲染动画

问题页动画这里的核心代码:
image

loadAnimation

这里渲染器一般用 svg 就可以了,兼容性也好。

1
2
3
4
5
6
7
const lottieInstance = lottie.default.loadAnimation({
container: dom, // 渲染到哪个 DOM
renderer: "svg", // 渲染器
loop: false, // 循环
autoplay: false, // 自动播放
animationData // Lottie JSON
});
绑定事件

要绑定事件首先就要找到需要绑定事件的元素,这里渲染出来的在 HTML 上是 SVG 标签,这里我是直接获取所有的g 标签,并为每个标签做上标记:

1
2
// debug: add symbol to each layer
layer.classList.add(`${i}`);

然后通过谷歌调试工具直接看哪个标签需要绑定事件,将它的下标记录到一开始定义的数据中,拿到对应的元素。
image

这里注意,因为 lottie 渲染出来的元素可能会重叠的,所以点击可能会被其他元素遮挡,在 addEventListener 的时候可以把事件流从冒泡改为捕获。

1
2
3
layer.addEventListener("click",() => {
onClick?.(idx);
}, true /* 是否捕获 */);

分享长图遇到的一些问题

这一次需要分享的图片与以往不同,需要分享一张高清长图。如果需要达到以下的清晰度,起码需要时一张二倍图,而H5本地生成图片分享方案因为浏览器本身的 canvas 限制不能实现(具体原因补充到文档里了)。
image

调服务端 Puppetter 方案

经过万般折腾,实在走投无路最终去调用了服务端的海报接口。
🔗 Yapi: 内部地址
🔗 Gitlab 仓库地址:内部地址

  • Puppeteer 简单说就是打开一个无头浏览器,通过的 DevTools 协议直接与浏览器通信,利用浏览器的内部截图机制。这个机制允许浏览器渲染出整个页面并将其截图,自然不会受到 canvas 的限制。
    最终好不容易满足了需求方的清晰度要求,考虑到接口可能失败,补了一段兜底的逻辑上去。(没写兜底的话可能已经在写报告了)
    突发流量
    活动一上线遇到了突发流量,加上服务端有个内存泄漏… CPU 持续满载,所有生成的图片失败,都从我这里走了兜底。好在最后前后端都及时调整,有惊无险。
    image

顺带在后续加上了日志,查了一下接口成功的日志 18000+ 条,错误 49 条。

微信分享

这里在调用服务端微信分享时意外发现调不起来,经一顿排查发现。微信 SDK 那边的分享图片关键有两个参数,一个是分享的图片,另一个是缩略图,如果缩略图超出限制,则无法调起。而现有情况发现客户端那边其实并不处理H5 这里传入的缩略图,而是自动压缩 H5 传入的主图作为缩略图,而这里的压缩算法并没有压缩到指定大小,导致传入的主图需要配合客户端的压缩比例而降低图片大小。比如说客户端的压缩一次只能压缩 50%,缩略图的限制是 100kb ,我们这里传入的图片最多就只能是 <= 200kb 。
这个问题也会在今后版本修复…

生成海报技术选型

本地生成海报

如果海报的尺寸小于一个屏幕,目前最好的解决方案还是通过前端本地生成。优点在于速度快,且几乎不出错。缺点就在于图片尺寸有限,还有一个问题就是有部分手机的字体缩放功能会直影响预浏览器的字体大小,导致截图的展示异常,但注意这里该设备原本展示的页面就有异常。

服务端生成海报

如果海报的尺寸大于 1.5 个屏幕且要求高清,可以考虑这个方案。它的优劣势就和前端截图相反了,优势在于没有图片尺寸上限,且不会被设备所影响;缺点是慢,可能出错,需要有兜底的逻辑。还有最重要的一点就是会占用服务端资源(成本问题),尽量做好充分沟通后再决定。

资源整理

因为我这块需要定义的数据贼多,光多语言就近 4000 条,每个语种 240 多条,图片资源也是上百张。
image

AI 生成

但在人工智能如此发达的今天,可以直接将这种活交给 AI 了。通过举例的方式,加上在后面多语言组的key值也十分语义化,所以 AI 也很智能地生成好了数据,这里不管是前期 mock 数据,还是后期替换真实数据,这都省去了我很多时间。
image