Lottie 动画的文件体积优化,JSON 精简技巧

Lottie 动画的文件体积优化,JSON 精简技巧

Lottie 动画的文件体积优化,JSON 精简技巧


Lottie 动画的文件体积优化,JSON 精简技巧


Lottie 在 Android 上的普及程度已经不用多说,Airbnb 开源这套方案近十年后,它几乎成了 App 里播放复杂动画的标准答案。但一个常被忽略的问题是:设计导出的 JSON 文件往往大得离谱。我见过最夸张的案例,一个 15 秒的引导页动画,After Effects 导出来 2.3MB,这放在 2018 年可能还能忍,现在用户点开设置里的「清除缓存」越来越频繁,网络环境也没想象中稳定,体积问题就成了实实在在的痛点。


这篇文章想聊的不是 Lottie 怎么用——那个文档很全了——而是怎么把 JSON 本身瘦下来,以及哪些工具能在这个过程中帮上忙。我会从实际踩过的坑出发,包括一些官方文档没写清楚的行为,和几家工具各自的边界。


为什么 JSON 会膨胀:从 After Effects 的图层结构说起


Lottie 的 JSON 本质上是对 After Effects 项目结构的完整序列化。设计师在 AE 里每建一个形状图层、每打一次预合成、每调一次贝塞尔曲线手柄,最终都会变成 JSON 里的节点。问题是 AE 的图层模型和运行时渲染模型并不完全对齐,很多对视觉无影响的信息会被原样保留。


最典型的例子是空图层和冗余分组。设计师为了组织方便,经常创建大量仅用于嵌套的父级图层,或者把一组形状打成一个预合成再引用。Bodymovin 插件(Lottie 官方提供的 AE 导出插件)默认会忠实地把这些结构全部导出,包括那些 nm(name)字段里写着「参考用勿删」的图层。这些名字字符串在运行时完全不会被读取,却占着字节。


另一个大头是路径数据。矢量形状在 AE 里以贝塞尔曲线描述,Bodymovin 导出时会把每个锚点、每个控制点的坐标都写成高精度浮点数。一个圆角矩形如果设计师是用钢笔手画的而不是用形状工具生成的,路径点的数量可能多出几倍,坐标精度也常常是 0.0001 级别,而实际渲染到 1080p 屏幕上,小数点后两位之后的数字几乎不产生可见差异。


还有位图嵌入的陷阱。Lottie 支持在动画里嵌入 base64 编码的 PNG 或 JPG,这本来是方便设计师把复杂渐变或光效打包进去。但太多团队滥用这个特性:一个 200x200 的模糊阴影,设计师直接导出 PNG 贴进去,base64 膨胀后可能比矢量描述本身还大几十倍。更隐蔽的是,有些 AE 插件在生成效果时会自动创建隐藏的位图资源,Bodymovin 可能把这些也带出来。


Bodymovin 的导出设置:第一道关卡,但不够


Bodymovin 本身提供了一些压缩选项,在 AE 的窗口 > 扩展 > Bodymovin 面板里,「Settings」标签页有几个值得调整的项。


「Glyphs」选项如果勾选,会把动画里用到的文字轮廓转换成矢量路径,这样客户端不需要依赖系统字体。但代价是体积显著增加,而且如果文字内容有变化(比如多语言),这些预转曲的路径就白占了空间。对于固定文案的动画,可以开;如果文案可能动态替换,建议关掉,让客户端用系统字体渲染,同时确保设计师在 AE 里用的是常见字体。


「Hidden」图层默认是导出的,这个选项在设置里叫「Export hidden layers」,建议关掉。很多 AE 项目里有大量备用的隐藏图层,设计师忘了删,Bodymovin 不会自动过滤。


「GUIDs」是一个容易忽略的选项。Bodymovin 会给每个图层生成唯一标识符,用于某些高级功能(比如运行时通过 id 查找图层)。如果动画不需要脚本控制,可以在设置里关掉 GUID 导出,能省下一批字符串。


但 Bodymovin 的这些设置属于「保守优化」,它不会改动你的图层结构,也不会压缩路径数据。一个经过上述设置调整的 JSON,可能从 2.3MB 降到 1.8MB,但离真正精简还有距离。而且 Bodymovin 的更新频率不算高,5.7 版本之后的大改动不多,一些长期存在的 issue(比如对特定表达式的不完全支持)至今还在 GitHub 上挂着。


lottie-web 的 gzip 假象:网络传输不等于运行时内存


很多团队看到 lottie-web 的文档里写「支持 gzip」,就以为问题解决了。实际上 gzip 解决的是网络传输环节的带宽,JSON 到了客户端内存里还是要完整解析的。Android 上的 lottie-android 库在解析时会把整个 JSON 读进 JSONObject,大文件直接导致 GC 压力和解析耗时。


我在一个项目里测过,一个 1.5MB 的 JSON,gzip 后 280KB 传输很快,但 LottieCompositionFactory.fromJsonString 的耗时在低端机上超过 800ms,主线程解析直接丢帧。后来改成异步预加载 + 缓存 LottieComposition 实例,才算缓解。但根本问题还是 JSON 本身太大,解析的 DOM 树太深。


所以传输压缩和运行时精简是两个维度,不能互相替代。下面要聊的工具,主要解决的是 JSON 结构本身的冗余。


Lottie-Optimizer:官方出的,但有点「老实」


Lottie 团队在 GitHub 上有一个不太起眼的仓库叫 airbnb/lottie-optimizer,这是一个 Node.js 写的命令行工具,做几类相对安全的自动优化:


  • 移除未使用的资源(比如被引用了但从未显示的图片)
  • 合并连续的关键帧如果它们值相同(比如透明度从 100 到 100 的中间帧)
  • 裁剪超出画布范围的形状路径
  • 把浮点精度降到合理位数

  • 安装很简单,npm install -g lottie-optimizer,然后 lottie-optimizer input.json output.json。我测试过一个 890KB 的动画,优化后 720KB,幅度约 19%。


    但这个工具的优化策略比较保守,它不会改动图层结构,也不会对路径做激进简化。它的 README 里明确写了「不会做任何可能改变视觉效果的修改」,这既是优点也是局限。对于需要严格还原设计稿的场景,它是安全的;但如果你的目标是尽可能小,它帮不上太多。


    另外这个工具已经两年多没有实质性更新,最后一条 commit 还在处理 Node 16 的兼容性。Lottie 的新特性(比如 5.10 之后加的 dotLottie 格式支持)它完全不涉及。我个人把它当成「保底检查」用,导完先跑一遍,确保没有明显浪费,但不做主要依赖。


    Lottie-SVG:一个被名字耽误的转换器


    lottie-svg 不是把 Lottie 转成 SVG 动画,而是另一个方向的优化工具,由一位独立开发者维护(GitHub: lottiefiles/lottie-svg,注意是 LottieFiles 这个组织下的项目,不是 Airbnb 官方)。它的核心思路比较独特:把 Lottie JSON 里用位图或复杂路径实现的效果,尝试用更简洁的 SVG 等价描述替换,再重新打包回 Lottie 兼容的 JSON。


    实际用起来,它对「渐变填充」这类效果的压缩很明显。AE 里的渐变在 Bodymovin 里通常被导出为大量色标关键帧,lottie-svg 会检测这种模式,替换成更紧凑的渐变定义。一个用了线性渐变背景的 300KB 动画,经过转换后降到 190KB,视觉上逐帧对比没有可察觉差异。


    但这个工具有几个硬坑。第一,它不支持所有 Lottie 特性,尤其是遮罩(Matte)和轨道遮罩(Track Matte)的复杂组合,转换后可能直接丢失图层。第二,它的版本锁定比较死,对 Bodymovin 5.5 之前导出的旧 JSON 支持反而更好,新格式有时报解析错误。第三,这个项目维护状态存疑,npm 上最新版本是 1.4.2,发布于 2021 年底,GitHub issue 区有不少未回复的 bug 报告。


    我的建议是:如果动画里没有遮罩、没有表达式驱动的复杂逻辑,可以试试看,但务必做逐帧 visual diff。LottieFiles 自己倒是还在用这个工具链处理上传到平台的动画,说明在受控场景下它是能工作的。


    LottieFiles 的在线压缩:方便,但别传敏感内容


    LottieFiles 平台(lottiefiles.com)提供了一键上传、在线压缩的功能,免费账户就能用。它的后端结合了多种策略,包括上述的 lottie-svg 转换,加上一些自家的私有优化算法。界面很直观,上传后显示原体积、优化后体积、压缩比,还能左右对比预览。


    我测过同一个 890KB 的文件,平台压到了 580KB,比 lottie-optimizer 的 720KB 激进很多。但预览对比时发现,一个用了「修剪路径」(Trim Paths)动画的描边效果,端点圆角从「圆角」变成了「斜角」,虽然差异细微,但对设计敏感的产品 unacceptable。


    后来翻他们的文档才知道,平台优化默认启用「路径简化」选项,会把贝塞尔曲线降采样,阈值设得偏激进。这个选项可以手动关掉,但免费用户每次都要重新配置,不会记住偏好。Pro 账户($15/月)可以保存预设,还能批量处理,对需要频繁出动画的团队可能值得,但个人或小项目没必要。


    最大的顾虑是数据安全。上传的 JSON 会经过 LottieFiles 的服务器,虽然他们的隐私条款说不会留存,但条款本身随时可以改。我们内部有动画涉及未发布产品的视觉元素,被安全团队明确禁止走任何第三方在线工具。这个限制在很多公司应该都存在。


    SVGO 的思路迁移:自己动手写 JSON 精简


    SVG 社区有一个非常成熟的优化工具叫 SVGO(SVG Optimizer),它的插件化架构允许对 SVG 的 XML 结构做各种激进精简。Lottie JSON 虽然不是 XML,但结构上是纯 JSON,完全可以借鉴类似的思路自己处理。


    我基于 SVGO 的插件模式写过一个内部用的 Lottie JSON 精简脚本,核心做了这几件事:


    浮点精度截断。遍历所有数字值,根据层级判断精度需求。坐标点(ks 下的 k 数组)保留 2 位小数,变换矩阵(a, p, s, r)保留 3 位,颜色值(c 下的 k)保留 4 位(因为 Lottie 的颜色是 0-1 浮点,4 位精度约等于 8bit 色深的 1/256,人眼基本不可分)。这个单项通常能省 15%-25% 体积,取决于原文件有多「慷慨」。


    移除运行时无关字段nm(name)、mn(match name)、cl(class)、ln(layer name)这些字段在 lottie-android 的解析里会被读取到对象里,但如果没有通过 LottieDrawable.setImageAssetDelegate 之类的方式按名字查找资源,它们就是纯内存开销。我的脚本会扫描确认没有 refId 依赖或表达式引用后,批量删除这些字符串。


    关键帧去重。Bodymovin 有时会在时间轴末尾生成一个和起始值相同的关键帧,形成循环暗示,但 Lottie 的 ip(in point)和 op(out point)已经定义了播放区间,这个重复帧可以安全移除。更隐蔽的是,两个相邻关键帧如果值完全相同且没有缓动曲线(h 为 1,即 hold 插值),中间的所有帧都是冗余的。


    预合成扁平化。如果预合成只被引用一次,且内部没有复杂的交叉引用,直接把它的图层列表合并到父级,去掉一层嵌套。这个操作需要谨慎,因为预合成有时用于局部坐标系隔离,但实测下来,设计师为了组织方便创建的「一次性预合成」非常多。


    这个脚本没有开源,但实现逻辑不复杂,用 Python 的 json 模块递归遍历就能做。难点在于建立「安全删除」的白名单,需要对 Lottie 的 JSON schema 有足够理解。官方 schema 在 lottie-docs 项目里有维护,但文档化程度一般,很多字段的语义需要结合 lottie-android 的解析代码反推。


    dotLottie 格式:不是压缩工具,但改变了存储方式


    2022 年左右,LottieFiles 主导推出了 dotLottie 格式(.lottie 文件),本质上是一个 ZIP 包,里面可以包含多个 JSON 动画、manifest 文件、以及共享的静态资源(比如多张 PNG 精灵图)。它解决的不是单个 JSON 的体积,而是「一组动画复用资源」的场景。


    比如一个 App 里有 10 个加载状态动画,都用了同一套圆点形状和配色。传统做法是 10 个独立的 JSON,每个都内嵌相同的矢量路径。dotLottie 允许把这些公共资源抽出来,动画 JSON 里通过 ref 引用,整体包体积比 10 个独立文件加起来小很多。


    Android 上的支持通过 dotlottie-android 库实现,目前最新是 0.5.0 版本,API 设计和 lottie-android 基本对齐,但有一些行为差异。比如它要求 manifest 里的 wh 必须和实际动画一致,而 lottie-android 对尺寸不匹配更宽容。另外这个库的依赖树里带了 Kotlin 协程和 Okio,对还在用纯 Java 或老版本 Kotlin 的项目可能引入冲突。


    dotLottie 的局限在于它是个「容器格式」,不是「优化算法」。如果你的动画本来就各自独立、没有共享资源,强行包成 dotLottie 反而多了 ZIP 头和 manifest 的开销。而且目前 Android Studio 的资源打包流程对 .lottie 后缀的自动处理还不完善,需要手动配置 aaptOptionsignoreAssetsPatternres 目录规则。


    一个实际案例:从 2.3MB 到 340KB 的路径


    回到开头那个 2.3MB 的引导页动画,这是我们 2023 年一个项目里的真实案例。优化过程分了几步,每步都有明确的取舍。


    第一步,让设计师回炉 AE。发现动画里有 3 个预合成实际上只包含静态背景,而且背景在整个 15 秒里完全不变。建议改成在客户端用纯色 View 做背景,动画 JSON 只保留前景元素。这一步设计师配合度是关键,很多设计师对「什么该在 AE 里做、什么该在代码里拼」没有概念,需要反复沟通。最终 AE 文件瘦身到 1.1MB 的 JSON。


    第二步,跑 lottie-optimizer,去掉隐藏图层和未引用资源,降到 980KB。


    第三步,用我的内部脚本做浮点截断和字段清理,降到 620KB。


    第四步,发现动画里有一段 4 秒的粒子效果,设计师用 50 个独立形状图层做的,每个图层有 30 个路径点。实际上这个粒子效果在视觉上是「模糊的一团」,和用 5 个图层、每个 6 个路径点的简化版逐帧对比,在 1080p 手机上几乎不可区分。设计师重新做了简化版本,JSON 降到 380KB。


    第五步,检查是否有 base64 嵌入,发现一个 150x150 的渐变噪点纹理,实际是 AE 某插件自动生成的。替换成客户端用 Shader 实现的等价效果,JSON 最终 340KB。


    这个案例里,工具只贡献了部分优化,最大的收益来自「设计层面的简化」和「代码层面替代」。这也是我想强调的点:JSON 精简不是纯技术问题,是设计和工程协作问题。如果设计师坚持「每个像素都要和稿一样」,工具能做的事很有限。


    运行时加载的辅助策略:不把鸡蛋放一个篮子


    除了 JSON 本身,加载策略也能缓解体积带来的体验问题。


    LottieCompositionCache 默认是内存缓存,可以配置最大缓存数和淘汰策略。对于体积大的动画,建议设置 cacheComposition(false),自己用 LruCache 管理,按业务优先级淘汰,而不是让 Lottie 的默认策略把内存占满。


    LottieDrawable 从 5.0 开始支持 setAsyncUpdates,允许在后台线程解析和渲染部分帧,对超大动画的首次播放有帮助,但会增加一点延迟。这个 API 在官方文档里写得比较简略,实际用起来要注意和 LottieAnimationViewsetImageDrawable 时序配合,否则可能闪白。


    还有一个偏门技巧:如果动画是循环播放的,且第一帧就是静态画面,可以先用 LottieDrawablesetFrame(0) 抓一帧缩略图显示,同时后台完整解析,这样用户不会看到空白等待。这个需要一点自定义代码,但对首屏体验提升明显。


    工具选择的个人偏好


    如果让我给一个优先级建议:


  • 必做:Bodymovin 导出时调好设置,关掉 hidden layers 和不需要的 GUIDs
  • 保底:跑一遍 lottie-optimizer,确保没有低级冗余
  • 可选:LottieFiles 在线压缩,仅用于非敏感内容,且要逐帧对比
  • 进阶:根据项目特性写内部脚本,做浮点截断、字段清理、结构扁平化
  • 特定场景:dotLottie 用于多动画共享资源,dotlottie-android 注意版本兼容性

  • lottie-svg 我现在基本不用了,维护状态和风险收益比不对。Lottie-Optimizer 虽然保守,但至少是官方维护,行为可预期。


    一个还没解决的痛点


    所有这些优化都是针对「静态 JSON」的。但 Lottie 正在往更动态的方向发展,比如 LottieFiles 在推的「交互式 Lottie」(通过 JSON 里的 markers 和外部脚本控制播放),以及社区里有人尝试把 Lottie 和 Rive 的 State Machine 概念结合。这些动态特性会让 JSON 结构更复杂,现有的精简工具大多不支持,或者支持后破坏了交互逻辑。


    另一个方向是 Lottie 的 Web 渲染器在实验 WebGPU 后端,如果未来 Android 端也跟进,JSON 里的某些数据结构可能会为了 GPU 友好而重新设计,届时优化策略又要调整。但这些都是猜测,目前没见到官方路线图。


    对于当下的 Android 项目,把现有 JSON 管好、和设计流程建立规范,比追新工具更实际。体积优化是个长期博弈,每次发版前花十分钟跑一遍检查脚本,累积下来的收益很可观。

    Systrace 和 Perfetto 的录制与解读 2026-07-15
    SavedStateViewModelFactory 的恢复逻辑,Bundle 大小限制 2026-07-15

    评论区