01. WHY
不少动画中,会带有一些业务数字,比如之前文章中提到的这段动画
这里的「3.39%」「2.43%」以及「1000 万」都属于业务数字,所以这段动画用 gif/apng/视频都不合适,因为数字有变动时候需要重新生成,而像余额宝的七日年化可是每天都会变化的,Lottie 真是这种场景下的最佳方案
02. HOW
要实现 Lottie 的文本动态修改,需要对 Lottie 的运行机制有一定了解
简单来说,运行前设计师导出的 lottie.json 对象,会被 lottie-web 解析之后产生相应的 JS 对象,并在动画播放期间,由 JS 对象计算并修改 HTML 中相应的 svg 元素属性,从而实现动画播放
因此如果要做文本动态修改,理论上在这 3 个阶段都是可以「动手脚」的注: 这里暂时忽略了 lottie canvas 模式,最后一节会补充说明
先给出一个简单的示例,看下本人自学三天 AE 导出的一个土味 lottie 动画
可以通过我们团队自制的 lottie 编辑器预览下效果
03.A. 修改 lottie.json
这是最容易想到的办法,lottie.json 内部描述了动画的所有细节,自然也就包含了动画中的那段文本,如果我们能找到相应的字段进行修改,也就可以实现文本替换了,简单看下 json 就能找到重点(虽然你可能完全不知道这些 key 是什么鬼)
查找下 lottie-web api 可以发现是支持直接传入 json 对象的,那么我们在外部自己发 ajax 请求获取 json 然后替换即可,代码如下
fetch('https://gw.alipayobjects.com/os/finxbff/2d0c4a95-568f-4923-bef0-e20fca6018ca/7abc1e3d-c381-49ed-ad54-3a48366f0180.json')
.then(resp => resp.text())
.then(text => {
// 简单演示替换
const newJSON = text.replace('${文本}', '拾亿');
lottie.loadAnimation({
animationData: JSON.parse(newJSON),
container: document.getElementById('app'),
loop: true
});
});
这种做法优点是简单粗暴,但是也有一些缺点
- 文本替换存在一定不确定性,比如上面例子如果不是设计师在 AE 中写入
${文本}
这样明确的占位符,这个方法可就没那么容易实现了,如果设计师配合度不高,那么这个方式可操作性就差了一些 - 这种方式无法做到「运行时」修改,也就是 lottie 解析播放后就无法再修改文本了,比如某些场景下可能需要先播放 lottie 的前一部分,用户交互产生数据后,再替换文本播放后一部分,那么这种方式就无法满足了
03.B. 修改 JS 对象
通过修改 lottie 解析后的运行时 JS 对象,理论上一样可以修改文本,官方其实提供了相应的 API,仔细查找的话在 lottie-web 的官方文档里有提及,请看 https://github.com/airbnb/lottie-web/wiki/TextLayer.updateDocumentData
对于上面的 lottie 可以这样替换
anim.addEventListener('DOMLoaded', () => {
anim.renderer.elements[0].elements[0].updateDocumentData({t:'拾亿'},0);
});
- 第一个参数表示如何替换文本,t 表示文本内容,这里还可以传入 s 修改大小,fc 修改颜色
- 第二个参数表示替换指定关键帧的文本,文本可以有关键帧,在动画过程中展示不同的文本,如果没有关键帧可以省略这个参数
不过这个方式的麻烦之处在于,需要对解析出的 JS 对象比较了解,比如上面例子里需要知道 elements[0].elements[0]
对应的是那段文本,这里有一个人肉查找的过程
幸而我们发现官方还提供了 lottie-api 这样一个神奇的运行时修改 lottie 的库,可惜文档缺失、甚至打包有问题,导致用的人也少,这篇文章甚至可能是中文世界里第一个提到这个库的
但是这个库着实强大,比如要寻找相应的 JS 对象就很简单,示例如下
anim.addEventListener("DOMLoaded", () => {
const api = lottie_api.createAnimationApi(anim);
const elements = api.getKeyPath("comp1,textnode"); // 查找对象
elements.getElements()[0].setText("拾亿");
});
这里 getKeyPath 的参数 comp1,textnode
是什么呢?看一下设计师的 AE 源文件就知道了
其实就是图层名称的拼接,这个 lottie 内部有两层结构,第一层是「合成」,名称叫 comp1,文本位于 comp1 的内部,名称叫做 textnode,于是逗号拼接下就可以找到了
而 elements.getElements()
可以获取到所有满足条件的 JS 对象,因为 lottie 中图层的名称并不唯一,有时候可能会找到多个对象,最后通过 setText 就可以简单修改文本了
这个方式我个人比较喜欢,因为通过看 AE 图层名称去拼接 keypath 比自己用 [0][1] 去一层层查找对象要方便和健壮很多,不过如果你没有 AE 怎么办?上面提到的我们团队的lottie 在线编辑器里,也是可以看到这些信息的,后续我们会再完善,提供一键查看 keypath 功能
最后图层名称其实也可以是中文,一样可以查找到
完整代码示例
注意: 由于 lottie-api 1.0.2 build 的问题,导致 import 时需要这样写import * as lottieAPI from "lottie-api/dist/lottie_api";才能正确引入,而 codesandbox 并不支持所以示例里用了 script 引入
这种方法相比第一种要繁琐一些,但是我更偏好这种,因为能够做到「运行期」替换
另外如果文本图层存在多个关键帧的话,setText() 方法可以传入第二个入参替换指定关键帧中的文案,这与 updateDocumentData 一致,因为底层调用的就是 updateDocumentData
03.C. 修改 svg 元素
这里需要提到 Lottie 中的一个鲜为人知的黑科技,如果设计师在 AE 图层命名时尾部加入 #xxx
,那么生成的 svg 元素就会有一个 id 属性为 xxx,比如
于是根据这个黑科技,我们可以这么写
anim.addEventListener("DOMLoaded", () => {
const element = document.getElementById("J_txt");
element.querySelector("tspan").innerHTML = "拾亿";
});
代码示例
这个方式有些黑科技,但是却是简单有奇效,但是这种方式有一些缺点,如果文本图层存在多个关键帧,那么这种方式比较难解决
04. 比较
综合比较三种方式,我更喜欢修改 JS 对象,因为基于中间产物的修改方式扩展性是最强的,比如能够支持运行期,还可以修改大小、颜色、替换指定关键帧等等
05. canvas 模式的问题
最上面提到先忽略 canvas 模式,那么这个模式有什么问题吗?这里又涉及到 lottie 的底层实现了
由于在 canvas 模式下绘制文本是很慢的,作者考虑后放弃了 drawText 的实现(https://github.com/airbnb/lottie-web/issues/250),转而在导出 json 时抽取字体库中的字形(Glyphs)路径放入文件,canvas 模式下通过路径绘图来画出文本
所以上述方案中的 C 肯定无法支持 canvas 模式,而 A/B 要支持则依赖于导出 JSON 时,要替换的文本字形是否已经存在于 JSON 中,因此从实际出发,一般 canvas 模式下就比较难实现替换文本了(鬼知道产品经理要替换成什么)
另外根据上面的原理,上面所有例子中,从 AE 导出 lottie 时都记得去掉「Glyphs」这个选项,并且指定具体的 font family,记得把这件事告诉你的设计师小伙伴
最后,下一篇预告,相信你应该能想到,动态替换 Lottie 中的图片