# Lottie-web

# 前言

动画需求在业务开发中是很常见的功能,无论是客户端开发、Web 开发、还是桌面端开发,为了产品有更好的用户体验,UED 设计的视觉效果也愈发的复杂,一般些简单的淡入淡出,旋转效果开发花费些时间即可搞定,甚至稍微复杂些的动画多花费些时间也能完成,但主要面临几个问题:

  1. 耗时成本太高:在开发工作量很多的情况下,对开发来说无疑是增加了一大波工作量,用身边的一些同事的说法就是,连正常的业务需求都开发不完,谁还去精雕细琢那些去;
  2. 吃力不讨好:开发吭哧吭哧写完了动画交予 UED、PD 去验收时很多时候会反馈说这实现的和设计稿差距有些多,很高概率会有多次的反工。

自己手写动画太费事,那就直接用一劳永逸的方法,UED 直接出 GIF 动画,开发直接贴 GIF 图片即可,对于开发开说几个小时的动画需求几分钟即可搞定,整体来说 GIF 是一种制作简单、来源广泛(很多软件都可以生成gif动画)、兼容性强(基本上所有浏览器都支持)的轻量级的方案。

但从产品的视角、用户视角来说 GIF 的动画的弊端也是非常多的,诸如:

  1. GIF 图片保存了每一帧的内容,因此造成图片很大;为解决图片过大的问题,大多数情况下都会采用压缩的方式较小体积,但这也造成了动画模糊、失真;
  2. 用户无法直接与动画本身做交互,如停止、开始、加速等交互,也无法直接修改动画元素的属性如背景色等;

既如此,那就再引入一种新的技术方案 Lottie (opens new window) , 感兴趣的可以先先睹为快 Lottie-Example (opens new window)

# Lottie

# 什么是 Lottie

Lottie (opens new window) 是 Airbnb 发布的一款开源动画库,它适用于 Android、iOS、Web 和 Windows 的库。 它提供了一套从设计师使用 AE(Adobe After Effects)到各端开发者实现动画的工具流。

简单来说就是 UED 和开发各司其职,UED 提供动画 json 文件即可, 开发者就可以直接运用在 iOS、Android、Web 和 React Native之上,无需其他额外操作。

今天对如何设计动画不做详细介绍,只是从前端的视角出发看看如何使用 Lottie,实现精致的动画。

# Lottie-Web

Lottie-Web (opens new window) 是 Lottie 在 web 端的技术方案。

Lottie-Web (opens new window) 提供了 SVG、Canvas 和 HTML 三种渲染模式,一般使用 Svg 或 Canvas 即可。

  1. SVG 渲染器支持的特性最多,也是使用最多的渲染方式。并且 SVG 是可伸缩的,任何分辨率下不会失真;
  2. Canvas 渲染器就是根据动画的数据将每一帧的对象不断重绘出来;
  3. HTML 渲染器受限于其功能,支持的特性最少,只能做一些很简单的图形或者文字,也不支持滤镜效果。

Lottie-Web-Image

# 使用

# 安装依赖

npm i lottie-web --save 
or 
yarn add lottie-web --save

# 实例化

以下方法提供了一个简单的初始化过程,其他参数可参考 官网 (opens new window)

function init(){
    // 读取动画容器
    const lottieContainer = document.querySelector("#container");
    if (!lottieContainer) return;
    // 实例化
    const lottieInstance = lottie.loadAnimation({
        // UED 提供的 动画的 json 文件
        path: 'https://static-cdn.canyuegongzi.xyz/lf20/lf20_jv0xz0qi.json',
        // 渲染方式
        renderer: "svg",
        // 是否循环
        loop: true,
        container: lottieContainer
    });
}

Lottie-Web-Image

至此, 开发者设计师可直接还原动画效果了,再也不会出现买家秀卖家秀的情况,也避免了开发和设计之前的互相扯皮,而且 JSON 文件,可以多端复用(Web、Android、iOS、React Native)。

# 动画控制

开始、结束动画

// 开始动画
function onStart() {
    lottieInstance?.play();
}

// 结束动画
function onStop() {
    lottieInstance?.stop();
}

速度修改

// 修改动画播放速度
function updateSpeed(val: number) {
    lottieInstance?.setSpeed(val);
}

Lottie-Web (opens new window) 提供了相当丰富的 Api 此处借花献佛从网络拷贝了一份常用的如下,其他的可参考官网。

  1. animation.play():播放,从当前帧开始播放;
  2. animation.stop():停止,并回到第0帧;
  3. animation.pause():暂停,并保持当前帧;
  4. animation.goToAndStop(value, isFrame):跳到某个时刻/帧并停止(isFrame(可省略,默认false:毫秒;true:帧)指明value的单位是毫秒还是帧);
  5. animation.goToAndPlay(value, isFrame):跳到某个时刻/帧并播放;
animation.goToAndStop(30, true)     // 跳转到第30帧并停止
animation.goToAndPlay(300)          // 跳转到第300毫秒并播放
  1. animation.playSegments(arr, forceFlag):以帧为单位,播放指定片段(arr可以包含两个数字或者两个数字组成的数组,forceFlag表示是否立即强制播放该片段);
animation.playSegments([10,20], false)          // 播放完之前的片段,播放10-20帧
animation.playSegments([[0,5],[10,18]], true)   // 直接播放0-5帧和10-18帧
  1. animation.setSpeed(speed):设置播放速度,speed为1表示正常速度;
  2. animation.setDirection(direction): 设置播放方向,1表示正向播放,-1表示反向播放
  3. animation.destroy(): 删除该动画,移除相应的元素标签等。

# 生命周期

Lottie-Web (opens new window) 提供了几个比较使用的生命周期钩子函数,开发者可根据需要做定制开发:

  1. data_ready:动画数据加载完毕;
  2. config_ready:完成初始配置后;
  3. data_failed:当无法加载动画的一部分时;
  4. loaded_images:当所有图像加载成功或错误时;
  5. DOMLoaded:将元素添加到DOM时。
animation.addEventListener('data_ready', () => { 
    console.log("data_ready")
});

# 进阶

上述章节中已经可以完美的运行 Lottie 动画了,但每个动画都得手动实例化,是不是很繁琐,此处笔者通过 WebComponent 技术方案做了进一步封装,方便开发者更简洁的调用, 先睹为快,快速体验 (opens new window)

# WuLottie 封装

import { extractClass } from '@wu-component/common';
import { h, Component, WuComponent, Prop, OnConnected, OnDisConnected, Watch } from '@wu-component/web-core-plus';
import lottie, { AnimationItem } from "lottie-web";
import css from './index.scss';

@Component({
    name: 'wu-plus-lottie',
    css: css,
})
export class WuLottie extends WuComponent implements OnConnected, OnDisConnected {
    constructor() {
        super();
    }

    public lottieInstance!: AnimationItem;

    public lottieContainer!: HTMLDivElement;

    @Prop({ type: Boolean, default: true })
    public loop: boolean;

    @Prop({ type: String, default: undefined })
    public data: string;

    @Prop({ type: Boolean, default: true })
    public autoplay: boolean;

    @Prop({ type: String, default: 'svg' })
    public renderer: 'svg' | 'canvas' | 'html'

    @Prop({ type: Object, default: {} })
    public config: Record<string, any>;

    public override connected(shadowRoot: ShadowRoot): void {
        this.init();
    }

    // 元素销毁时也同步销毁掉 lottie 动画
    public override disConnected(): void {
        this.lottieInstance.destroy();
    }

    private init(){
        // 实例化动画
        this.lottieContainer = this.shadowRoot.querySelector('.lottieWrapper');
        if (!this.lottieContainer) return;
        this.lottieInstance = lottie.loadAnimation({
            // @ts-ignore
            ...this.$reactive || {},
            path: typeof this.data === 'string' ? this.data : undefined,
            animationData: typeof this.data === 'object' ? this.data : undefined,
            container: this.shadowRoot.querySelector('.lottieWrapper'),
        });
    }

    @Watch('data')
    public dataChnage(val: string, old: string) {
        this.init();
    }

    @Watch('loop')
    public loopChnage(val: boolean, old: boolean) {
        if (!this.lottieInstance) return;
        this.lottieInstance.loop = val;

        if (val && this.lottieInstance.isPaused) {
            this.lottieInstance.play();
        }
    }

    @Watch('autoplay')
    public lautoplayChnage(val: boolean, old: boolean) {
        if (!this.lottieInstance) return;
        this.lottieInstance.autoplay = val;
    }

    public stop() {
        return this.lottieInstance && this.lottieInstance.stop();
    }

    public play() {
        return this.lottieInstance && this.lottieInstance.play();
    }

    public override render(_renderProps = {}, _store = {}) {
        return (
            <div {...extractClass({}, 'lottieWrapper', {})}> </div>
        );
    }
}


# 如何使用

# 安装依赖

npm i l@wu-component/wu-lottie --save 
or 
yarn add @wu-component/wu-lottie --save

# 使用

开发者无需手动实例化 Lottie, 可如同普通 HTML 标签般接入。

<wu-plus-lottie data="https://cdn.canyuegongzi.xyz/wu-component-static/lf20_r6blppzq.json"></wu-plus-lottie>

# 思考

了解了如何使用 Lottie ,但是在实际应用中也有一些优势和不足,要按照实际情况进行取舍。

  1. Lottie-web 文件本身仍然比较大,未压缩大小为 513k,轻量版压缩后也有 144k,经过 Gzip 后,大小为39k。所以,需要注意 Lottie-web 的加载;
  2. 效果完全依赖设计师,结果需要开发把关,如最后的 json 文件大小;
  3. 部分AE特效不支持。有少量的 AE 动画效果,Lottie 无法实现,需要格外注意 supported-features (opens new window)

虽然如此,Lottie 还是相当优秀的动画方案,多种技术方案多条路嘛,大家在日常工作中都在使用什么技术方案欢迎在评论区讨论。

方案中涉及的代码均可在 github (opens new window) 阅读,欢迎 Star。