飞书中 Lottie 动画的应用

7,506 阅读5分钟

背景

飞书项目中 loading 常用的动画方案是 Gif 动画。

Gif 动画存在一定的问题,Gif 文件一般较大,且呈现的大小是固定的,无法缩放以匹配大屏幕和高密度屏幕,容易有锯齿,不能控制动画。

其他常用的动画方案:

Png 序列帧:合成的雪碧图文件大,且在不同屏幕分辨率下可能会失真

SVG 动画: 实现成本高,容易出现动画还原度低的情况

目前项目中需要一种更加简单、高效、性能好、还原度高的动画方案,设计同学正在推动 AE + bodymovin 导出动画配置的方案,经过调研发现 Lottie 动画是一种可行性较高的方案。

Lottie 简介

Lottie是 airbnb 开源的可应用于Android,iOS,Web,React NativeWindows动画库, 本质上是一套跨平台的动画解决方案。它提供了一套完整的从 AE 到各个终端的工具流,通过 AE 的 Bodymovin 插件将设计师做的动画导出成一套定义好的 json 文件,之后再通过 Lottie 各端的库就可以实现动画效果,动画还原度 100%,Lottie-web Exampleimage.png

使用方法

lottie-web支持特性最多(airbnb.io/lottie/#/su…),可以实现较为复杂的动画,控制动画的播放,监听动画各个阶段的事件(github.com/airbnb/lott…

lottie-web的使用方法, 有三种渲染方式 svg, canvas, html, 一般常用 svg, canvas

lottie.loadAnimation({

  container: element, // the dom element that will contain the animation

  renderer: 'svg',

  loop: true,

  autoplay: true,

  path: 'data.json' // the path to the animation json

});

react-lottie的使用方法(将lottie-web封装成 React 组件)

import React from 'react'

import Lottie from 'react-lottie';

import * as animationData from './pinjump.json'

export default class LottieControl extends React.Component {



  constructor(props) {

    super(props);

    this.state = {isStopped: false, isPaused: false};

  }



  render() {

    const buttonStyle = {

      display: 'block',

      margin: '10px auto'

    };



    const defaultOptions = {

      loop: true,

      autoplay: true,

      animationData: animationData,

      rendererSettings: {

        preserveAspectRatio: 'xMidYMid slice'

      }

    };



    return <div>

      <Lottie options={defaultOptions}

              height={400}

              width={400}

              isStopped={this.state.isStopped}

              isPaused={this.state.isPaused}/>

      <button style={buttonStyle} onClick={() => this.setState({isStopped: true})}>stop</button>

      <button style={buttonStyle} onClick={() => this.setState({isStopped: false})}>play</button>

      <button style={buttonStyle} onClick={() => this.setState({isPaused: !this.state.isPaused})}>pause</button>

    </div>

  }

}

控制动画播放的方法:

名称描述
animation.play播放该动画,从目前停止的帧开始播放
stop停止播放该动画,回到第 0 帧
pause暂停该动画,在当前帧停止并保持
goToAndStopanimation.goToAndStop(value, isFrame);跳到某个时刻/帧并停止。isFrame(默认 false)指示 value 表示帧还是时间(毫秒)
goToAndPlayanimation.goToAndPlay(value, isFrame);跳到某个时刻/帧并进行播放
goToAndStopanimation.goToAndStop(30, true);跳转到第 30 帧并停止
playSegmentsanimation.playSegments(arr, forceFlag);arr 可以包含两个数字或者两个数字组成的数组,forceFlag 表示是否立即强制播放该片段 animation.playSegments([10,20], false);播放完之前的片段,播放 10-20 帧 animation.playSegments([[0,5],[10,18]], true);直接播放 0-5 帧和 10-18 帧
setSpeedanimation.setSpeed(speed);设置播放速度,speed 为 1 表示正常速度
setDirectionanimation.setDirection(direction);设置播放方向,1 表示正向播放,-1 表示反向播放
destroyanimation.destroy();删除该动画,移除相应的元素标签等。在 unmount 的时候,需要调用该方法

监听事件:

名称描述
data_ready加载完 json 动画
complete播放完成(循环播放下不会触发)
loopComplete当前循环下播放(循环播放/非循环播放)结束时触发
enterFrame每进入一帧就会触发,播放时每一帧都会触发一次,stop 方法也会触发
segmentStart每进入一帧就会触发,播放时每一帧都会触发一次,stop 方法也会触发
DOMLoaded动画相关的 dom 已经被添加到 html 后触发
destroy将在动画删除时触发

Lottie 动画性能测试

Lottie 局部加载动画, Gif 动画 与 Lottie 动画比较Gif 动画性能image.png

1,加载编译库文件的耗时(阻塞启动)

平均:25ms

2,lottie-web 文件本身的内存占用

shadow size: 136B Retained Size: 1209553B

3,执行时的耗时(体现到耗时上,CPU 采样会很不精准,阻塞业务逻辑,如启动 chat)

平均: 15.4ms

Lottie 动画性能

由上图可知,查看了 Gif 动画、Lottie 动画方案的 FPS、CPU 占用率、GPU 占用、Scripting、Rendering、Painting、内存的使用情况。

方案大小FPSCPU 占用率GPU 占用内存
Gif 动画279KB8-60fps, 多数帧率 50fps, 帧率波动较多0%28.6MB94527
Lottie 动画4KB (json 文件)242.2KB( lottie-web js 文件)20-60fps, 多数帧率 59fps,帧率较稳定,波动少0%21.1MB94825

通过以上数据分析,可知 Lottie 动画的配置文件较小,帧率较高,GPU 占用低,内存与 Gif 动画相差不大,性能较好。

Lottie 动画方案简单、高效、性能好,可以替代传统的 GIF 和帧动画,灵活利用好提供的属性和方法可以控制动画的播放。

注意事项

及时卸载 Lottie 动画组件

在不需要 Lottie 动画时,需要及时卸载 Lottie 动画组件

飞书出现过偶现 CPU 升高的异常案例,经过排查定位到是 Lottie 动画没有卸载引起的 CPU 升高。页面中已经没有动画,但 Lottie 一直在调用 requestAnimationFrame,导致在没有任何操作的情况下,CPU 占用升高至 2%-5% 左右,一般情况在没有任何操作的情况下,cpu 占用 0.1% ~0.2%。

Lottie 动画调用 react-lottie 组件,组件在 componentWillUnmount 时,会销毁该动画实例。

飞书中 CPU 升高的时候,发现 Lottie 动画中有动画实例尚未销毁,导致会不停的调用 requestAnimationFrame,导致异常的动画是局部加载动画。飞书中用到局部加载的动画的模块有主端(切换会话、联系人页面 、新添加的联系人、机器人、外部联系人、onCall、升级提示弹窗、添加联系搜索、发送云盘文件弹窗、Pin 列表、Docs Webview 加载动画、切换租户)、日历、应用中心等。

经过定位发现,应用中心里用到了局部加载动画, 在不需要动画的时候,没有卸载组件,只是通过 CSS 来隐藏了组件,导致没有销毁 Lottie 动画实例,requestAnimationFrame 会一直执行,代码如下:

// AppHome.js

 <div className={!isLoaded ? 'app-home-loadingImg' : 'display-none'}><PartialLoading /></div>

image.png

不需要 Lottie 动画的时候,卸载 Lottie 动画组件。

// AppHome.js

{

      !!isLoading && (

        <div className='app-home-loadingImg'>

          <PartialLoading />

        </div>

      )

}