洛蒂(Lottie)
#javascript #animation #lottie #lottieweb

在您开始阅读本文之前,请花15秒访问what is lottie?页面。这是探索洛蒂的绝佳起点。

为什么我们需要了解引擎盖下的内容?

Lottie无处不在。

Image description

Image description

目前,有关Lottie主题的文章可用。您可以通过各种平台探索它们:

这些来源中的每一个都提供了关于为什么以及如何在不同环境中利用洛蒂的宝贵见解。

lottie受到以下原因的高度评价:

  • 它拥有一个小的文件大小。
  • 它具有无限的可伸缩性。
  • 它在各种平台上都享有支持。
  • 用lottie创建的动画可以是互动的。

最初,Lottie被设计为将Adobe After Effects动画转换为与特定平台兼容的格式的工具。但是,它已演变为独立的规范,不再受Adobe After Effects术语的约束。您可以找到Lottie specification

最近,LottieFiles推出了plugin for figma,进一步扩大了Lottie的受欢迎程度和潜在的增长。这一发展表明Lottie逐渐成为行业标准,这对社区来说是一个很好的消息。

几乎每篇讨论Lottie的文章都强调了其生态系统,并赞扬其易用性。它确实是一种有效实现其目的的简单工具。但是,让我们将注意力转移到可能出现的挑战中。

我在Lottie方面的个人经验专门与Web开发有关,因此这些观察结果可能与其他环境无关。

如果您打算将Lottie Animations纳入Web项目,则官方文档将引导您到lottie-web

社区

让我们查看顶级贡献者的列表:

Image description

  • 首先,1602年的贡献是“ Bodymovin/Lottie”的创建者bodymovin。最重要的贡献者是图书馆的创建者也就不足为奇了。
  • 以250个贡献排在第二位的是Hernan-dev,这可能是同一个人。
  • 第三名,有15个贡献是knekne 谁创建了有用的介绍性video about using lottie on the web

在其余的贡献者中,只进行了80个提交,不到总数的5%。这些提交中的大多数可能与动画播放,寻求,应用转换或优化绘图直接相关。尽管这些提交无疑很重要,但看来该代码的主要大部分主要由bodymovin

维护

问题

Image description

一方面,Lottie的维护者通过解决许多问题而做得很好。但是,值得注意的是,有600个公开问题的存在确实表明存在持续的挑战和需要关注的领域。这表明仍有工作要解决这些问题。

测试

截至2023年5月我的知识截止,Lottie-web在其GitHub存储库上没有任何测试。测试可能是在Airbnb's内部代码库中实施的,而不是公开可见的。但是,重要的是要注意,自您开始撰写本文以来,维护者添加了some tests,这是一个幸运的巧合。这凸显了这样一个事实,即由于缺乏验证机制,对Lottie的内部做出贡献是充满挑战的。令人鼓舞的是,已经采取了通过包括测试的步骤来解决这个问题。

特征

Lottie-web does not support all features lottie格式描述。

Image description

有不同问题的不同渲染器

Lottie-web提供了为2D Canvas Context,SVGHTML渲染动画的支持。

这些渲染器中的每一个都提出了自己的一系列挑战。例如,当使用帆布渲染器时,可以通过使用transferControlToOffscreen将动画播放到Web Worker中,从而优化动画播放。这可以显着提高动画逻辑的性能。但是,SVG渲染器不支持此功能,因为无法从web worker中访问DOM

另一方面,SVG渲染器提供了诸如硬件加速模糊过滤器之类的功能,从而使更新SVG元素相对较快。另一方面,帆布渲染器缺乏对简单模糊过滤器的内置支持。此外,如果SVG包含许多对象,则可能会影响性能。因此,在播放动画时,必须考虑到每个渲染器的功能和局限性,必须在性能和所需功能之间取得平衡。

您可以找到有关for example的不同文章

甚至service which optimizes your lottie
and this,LOL

确实值得注意的是,许多文章专注于使用各种技术优化Lottie文件及其结构。在某些情况下,这些优化可能需要对原始After Effects文件进行修改。

Readme says

正在进行更多的优化,但尽量不要在AE中使用巨大的形状来掩盖其中的一小部分。
太多的节点也会影响性能。

但是为什么在网络中播放动画可能是一个问题?...

webglwebgpu没有渲染器

想象您正在使用WebGL创建一个2D游戏。将After Effects中创建的动画纳入您的游戏将是很棒的。尽管您可以使用帆布元素绘制动画,但它缺乏某些功能。另一个选择是将动画作为SVG渲染,但是要在WebGL上下文中使用它们,您需要序列化SVG,将其转换为图像,然后将该图像上传到WebGL。不幸的是,目前还没有理想的解决方案。

实际上,这并不完全准确。 lottie-web
图书馆本身不支持渲染为WebGL。但是,有一个称为canvaskit-wasm的软件包,该软件包将Skia(图形引擎)与WebAssembly(WASM)包装。该软件包包括一个名为skottie的模块,该模块支持将动画渲染到WebGL表面。但是,这种方法存在缺点:使用wasm需要加载一个相对较大的软件包,并且不确定是否正确支持所有功能,因为在不同平台上跟踪lottie支持的the official compatibility table不包括skottie

>

历史

Image description

我经常想知道为什么这项技术仅在2015年才引入。这个概念似乎很简单,不是吗?好吧,也许不是。我的猜测是,在这种特定类型的软件方面没有很多开发人员。与图形有关的任何事物通常都会像一个神秘的境界一样,几乎像黑魔法一样,尤其是对于像我这样的人。

作为一个社区,我们应该做什么?

很明显,lottie-web项目需要帮助。

我们如何提供帮助?

  • Donate在财务上贡献是支持该项目的一种方法。即使是每月少量捐款也可以有所作为。就我个人而言,我每月捐款5美元,我希望bodymovin考虑我在享受我的捐款资助的一杯咖啡时所打开的问题。
  • contribute 如果您拥有知识和技能,那么直接为项目贡献是另一种协助的宝贵方式。但是,请记住,如果您不熟悉动画基础知识和lottie格式,贡献可能会具有挑战性。这正是本文旨在解决的问题!

神秘的lottie

我做了一个workshop,我试图从头开始构建Lottie Player。
Another视频对您来说可能很有趣,它是关于生成视频的,但是同时它与我要分享的信息重叠。

我们的计划:

  • 在After Effects中创建简单的动画
  • 使用bodymovin将动画导出到JSON
  • 探索JSON文件及其结构的内容
  • 创建演示,我们在其中使用lottie-web渲染视频
  • 用我们的自定义渲染器替换为webgl
  • 将我们的渲染器设计与Lottie-Web的设计进行比较

项目效果

让我们在After Effects中创建一个简单的动画

  • 在After Effects中创建项目

  • 设置构图

构图是电影的框架。每个构图都有自己的时间表。一个典型的构图包括代表组件的多个层,例如视频和音频录像项,动画文本和矢量图形,静止图像以及灯光

Image description

  • 创建实心层

Here您可以找到可以与After Afters创建的不同层的概述。

让我们创建基本的纯色层。

Image description

在这里我们可以指定其颜色,宽度和高度。
Image description

好吧,这是我们的动画:

Image description

但是没有动画!

动画是在屏幕上发生变化的时候,对吗?
我们可以改变什么?

如果单击一个实心层,您可以看到它支持transform

Image description

您可以更改positionrotationopacityscaleanchor point。让我们更改某个:

Image description

它仍然不是动画,因为我们更改了第一帧的位置。

是时候添加密钥帧了:
让我们将矩形移至右下角。

Image description

这是什么意思?
在时间(0)(第一个密钥帧)中,我们声明了层应具有位置(0,0)
在时间(2s)(第二个密钥帧)上,我们声明了位置(1920,1080)。在0到2秒之间的时间内将使用线性函数插值。

问题,我们如何定义无线动画?
为此,我们可以使用bezier curves

在此屏幕截图上,我们已经打开了曲线编辑器,将曲线分裂为位置(我们将稍后讨论)。

Image description

因此,我们可以控制xy坐标在时间期间将如何更改。为此,我们使用了cubic bezier

什么是cubic bezier? Wikipedia有一个great explanation

Image description

要定义这样的曲线,我们只需要定义2个点,通常称为inout control points

例如this curve
有2个控制点:

  • x = 0,y = 1
  • x = 1,y = 0 Image description

请注意,我们还可以使用cubic bezier定义线性动画。只需放置control points to diagonal

Image description

为了简化我们的第一个动画,让我们回到线性动画

Image description

好吧,让我们将动画存储到JSON,因为我们使用bodymovin
关注plugin installation steps

Image description

洛蒂结构

让我们看一下保存的文件:

{
    "v": "5.10.2",
    "fr": 29.9700012207031,
    "ip": 0,
    "op": 900.000036657751,
    "w": 1920,
    "h": 1080,
    "nm": "Moving square",
    "ddd": 0,
    "assets": [],
    "layers": [
        {
            "ddd": 0,
            "ind": 1,
            "ty": 1,
            "nm": "Red Solid 1",
            "sr": 1,
            "ks": {
                "o": {
                    "a": 0,
                    "k": 100,
                    "ix": 11
                },
                "r": {
                    "a": 0,
                    "k": 0,
                    "ix": 10
                },
                "p": {
                    "a": 1,
                    "k": [
                        {
                            "i": {
                                "x": 0.833,
                                "y": 0.833
                            },
                            "o": {
                                "x": 0.167,
                                "y": 0.167
                            },
                            "t": 0,
                            "s": [
                                0,
                                0,
                                0
                            ],
                            "to": [
                                0,
                                0,
                                0
                            ],
                            "ti": [
                                0,
                                0,
                                0
                            ]
                        },
                        {
                            "t": 15.0000006109625,
                            "s": [
                                1920,
                                1080,
                                0
                            ]
                        }
                    ],
                    "ix": 2,
                    "l": 2
                },
                "a": {
                    "a": 0,
                    "k": [
                        150,
                        150,
                        0
                    ],
                    "ix": 1,
                    "l": 2
                },
                "s": {
                    "a": 0,
                    "k": [
                        100,
                        100,
                        100
                    ],
                    "ix": 6,
                    "l": 2
                }
            },
            "ao": 0,
            "sw": 300,
            "sh": 300,
            "sc": "#ef0c0c",
            "ip": 0,
            "op": 900.000036657751,
            "st": 0,
            "bm": 0
        }
    ],
    "markers": []
}

它包含许多名为字段的一两个符号。它已经被拉开了!

为了了解所有领域的含义,我们可以使用此lottie schema
可以方便的另一件事是json editor
实际上,lottiedocumentation非常好,您可以阅读并跳过本文ð

lottie文件的根级别在规范中称为animation

顶级对象,描述动画

让我们专注于它

{
    "v": "5.10.2",
    "fr": 29.9700012207031,
    "ip": 0,
    "op": 900.000036657751,
    "w": 1920,
    "h": 1080,
    "nm": "Moving square",
    "ddd": 0,
    "assets": [...],
    "layers": [...],
    "markers": [...]
}
  • v-版本。它用于版本控制数据,根据SEMVER
  • fr-帧帧每秒
  • ip-“点”,哪个动画开始(通常为0)
  • op-“ out point”,框架动画停止/循环,这使得在ip为0的框架中的持续时间为0”
  • w-动画的宽度
  • h-动画高度
  • nm-名称,从编辑中可以看出
  • ddd-动画是否具有3D层
  • layers-层

所以,我不会将整个规范复制到本文中,主要思想,使用它来理解lottie的内容。

一些IDE支持模式
例如,在vscode中,您可以添加vscode/settings.json

{
    "json.schemas": [

        {
            "fileMatch": [
                "src/animations/*.json"
            ],
            "url": "https://lottiefiles.github.io/lottie-docs/schema/lottie.schema.json"
        }
    ]
}

,您将在IDE内部获得JSON的每个属性的描述。
Image description

用顶级对象清除的一切。

layers内部有什么?
只有一层:

{
    "ddd": 0,
    "ind": 1,
    "ty": 1,
    "nm": "Red Solid 1",
    "sr": 1,
    "ao": 0,
    "sw": 300,
    "sh": 300,
    "sc": "#ef0c0c",
    "ip": 0,
    "op": 900.000036657751,
    "st": 0,
    "bm": 0,
    "ks": {
        "o": {
            "a": 0,
            "k": 100,
            "ix": 11
        },
        "r": {
            "a": 0,
            "k": 0,
            "ix": 10
        },
        "p": {
            "a": 1,
            "k": [
                {
                    "i": {
                        "x": 0.833,
                        "y": 0.833
                    },
                    "o": {
                        "x": 0.167,
                        "y": 0.167
                    },
                    "t": 0,
                    "s": [
                        0,
                        0,
                        0
                    ],
                    "to": [
                        0,
                        0,
                        0
                    ],
                    "ti": [
                        0,
                        0,
                        0
                    ]
                },
                {
                    "t": 15.0000006109625,
                    "s": [
                        1920,
                        1080,
                        0
                    ]
                }
            ],
            "ix": 2,
            "l": 2
        },
        "a": {
            "a": 0,
            "k": [
                150,
                150,
                0
            ],
            "ix": 1,
            "l": 2
        },
        "s": {
            "a": 0,
            "k": [
                100,
                100,
                100
            ],
            "ix": 6,
            "l": 2
        }
    }
}
  • ty-层类型,在我们的情况下是1,这意味着根据架构
  • sw-
  • sh-高度
  • sc-颜色,在外面是#ef0c0c,它并不完全是红色!好的,效果后,我得到了你。

Image description

让我们现在跳过其他属性,让我们专注于重要的事情-ks

ks-是一个Transform对象,负责为此layer动画。

{
    "o": {
        "a": 0,
        "k": 100,
        "ix": 11
    },
    "r": {
        "a": 0,
        "k": 0,
        "ix": 10
    },
    "p": {
        "a": 1,
        "k": [
            {
                "i": {
                    "x": 0.833,
                    "y": 0.833
                },
                "o": {
                    "x": 0.167,
                    "y": 0.167
                },
                "t": 0,
                "s": [
                    0,
                    0,
                    0
                ],
                "to": [
                    0,
                    0,
                    0
                ],
                "ti": [
                    0,
                    0,
                    0
                ]
            },
            {
                "t": 15.0000006109625,
                "s": [
                    1920,
                    1080,
                    0
                ]
            }
        ],
        "ix": 2,
        "l": 2
    },
    "a": {
        "a": 0,
        "k": [
            150,
            150,
            0
        ],
        "ix": 1,
        "l": 2
    },
    "s": {
        "a": 0,
        "k": [
            100,
            100,
            100
        ],
        "ix": 6,
        "l": 2
    }
}
  • o-不透明度
  • r-旋转
  • p-位置
  • a-锚点
  • s - scale

我们在after effects transform对象中看到的所有属性。

它们每个都包含

a属性,说它是动画属性。
如果不是动画属性,则意味着没有keyframes,值为静态。

在我们的示例中,只有一个动画属性。
如果属性不是动画的k包含静态值。

所以,在我们的示例中

  • opacity = 100
  • rotation = 0
  • koud51 = [150,150,0]
  • >
  • scale = [100, 100, 100]

position是动画的,因此k包含keyframes

[
    {
        "i": {
            "x": 0.833,
            "y": 0.833
        },
        "o": {
            "x": 0.167,
            "y": 0.167
        },
        "t": 0,
        "s": [
            0,
            0,
            0
        ],
        "to": [
            0,
            0,
            0
        ],
        "ti": [
            0,
            0,
            0
        ]
    },
    {
        "t": 15.0000006109625,
        "s": [
            1920,
            1080,
            0
        ]
    }
]

它描述了2个关键帧。

对于t(time)= 0,s(state/value)为[0,0]表示我们在场景左上角的层
对于t(time)= 15.000000610962,(状态/值)为[1920,1080]是指我们在场景右下角的一层

很容易,对吧?

它比您想象的要复杂

第一个密钥帧包含其他属性:iototi

这是什么意思?

正如我之前提到的,after effects支持两种不同的位置模式。

splittedcombined(这不是after effect术语)。

使用splitted模式x,y坐标已分开关键帧定义。

在这种情况下,我们将拥有这样的p(位置)对象:

{
    "s": true,
    "x": {
        "a": 1,
        "k": [
        ],
        "ix": 3
    },
    "y": {
        "a": 1,
        "k": [
        ],
        "ix": 4
    }
}

s意味着分裂。

让我们考虑p.x(position.x)

[
    {
        "i": {
            "x": [
                0.833
            ],
            "y": [
                0.833
            ]
        },
        "o": {
            "x": [
                0.167
            ],
            "y": [
                0.167
            ]
        },
        "t": 0,
        "s": [
            0
        ]
    },
    {
        "t": 15.0000006109625,
        "s": [
            1920
        ]
    }
]

一切都很简单
对于t = 0值= 0
对于t = 15.0000006109625,值= 1920

{
    "i": {
        "x": [
            0.833
        ],
        "y": [
            0.833
        ]
    },
    "o": {
        "x": [
            0.167
        ],
        "y": [
            0.167
        ]
    }
}

i-指立方体bezier的控制点
o-表示立方bezier的控制点

我们之前已经讨论了这是什么意思

使用此值,这是一个线性动画。

假设我们需要计算

framenumber = 7的值,可以像。
一样计算。

const bezier = new cubicBezier({in: [0.83, 0.83], out = [0.167, 0.167]});

// progress between 2 frames is a value between 0 and 1;
const progress = frameNumber / (15.0000006109625 - 0);

const x = bezier.get(progress) * (1920 - 0);

y也是如此。
请注意,相同的插值逻辑适用于不透明度,旋转,比例等。

让我们回到带有combined位置的最初情况

什么是iototi

在此模式下,after effects揭示了描述位置曲线的bezier曲线,并且与插值不相同。

transform.position插值设置中,我们可以选择我们想要的插值类型。
Image description

它不会改变将其导出到lottie的方式,因为,正如我之前提到的,也可以用bezier曲线表示线。

让我们考虑以下示例:
Image description

在导出的json

您可以看到tito描述了bezier 2d路径的控制点。

{
    "to": [
        0,
        1080,
        0
    ],
    "ti": [
        0,
        -540, // notice that it's relative to second keyframe value
        0
    ]
}

使用此模式,tito仅描述路径,但是动画播放器应该如何理解我们应该在该path上移动layer的速度?

Lottie将信息存储在io属性中

在After Effects中,它可以在keyframe速度设置中进行更改

Image description

我们将考虑如何在这种情况下插值位置。

好吧,现在我们知道了lottie的所有重要属性的含义。我们了解有关顶级对象的一切,知道层存储的方式,转换对象的工作方式以及如何组织键框架以及如何基于该数据插值值。

我认为我们已经准备好从头开始创建我们的自定义lottie渲染器。

让我们在下一部分中做