paint-brush
Developer Puts Lottie Under the Microscope by@stepancar
New Story

Developer Puts Lottie Under the Microscope

by Stepan MikhailiukDecember 24th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Lottie was designed as a tool to convert `Adobe After Effects` animations into a format compatible with specific platforms. Lottie has evolved into an independent specification that is no longer bound by the specification of ` adobe after effects' Lotties can be used to create interactive animations with infinite scalability.
featured image - Developer Puts Lottie Under the Microscope
Stepan Mikhailiuk HackerNoon profile picture

Before you begin reading this article, please take 15 seconds to visit the what is lottie? page. It's an excellent starting point for exploring Lottie.

Why do we need to understand what is under the hood?

Lottie is everywhere.

Image description

Image description

Currently, there are numerous articles available on the topic of Lottie. You can explore them through various platforms:


Each of these sources provides valuable insights into why and how to utilize Lottie in different environments.


Lottie is highly regarded by many for the following reasons:

  • It boasts a small file size.
  • It offers infinite scalability.
  • It enjoys support across various platforms.
  • Animations created with Lottie can be interactive.


Originally, Lottie was designed as a tool to convert Adobe After Effects animations into a format compatible with specific platforms. However, it has evolved into an independent specification and is no longer bound by Adobe After Effects terminology. You can find the Lottie specification.


Recently, LottieFiles introduced a plugin for figma, further expanding the popularity and potential growth of Lottie. This development signifies that Lottie is gradually becoming an industry standard, which is fantastic news for the community.


Nearly every article discussing Lottie highlights its ecosystem and praises its ease of use. It is indeed a straightforward tool that effectively accomplishes its purpose. However, let's shift our focus to the challenges that may arise.


My personal experience with Lottie pertains specifically to web development, so these observations may not be relevant to other environments.


If you intend to incorporate Lottie animations into a web project, the official documentation will direct you to lottie-web.

Community

Lets look at list of top contributors:

Image description

  • In the first place with 1602 contributions is bodymovin, the creator of the "bodymovin/lottie". It's not surprising that the top contributor is the library's creator.
  • Coming in second place with 250 contributions is Hernan-dev It's likely the same individual.
  • In the third place with 15 contributions is knekne who created a helpful introductory video about using lottie on the web


Out of the remaining contributors, only 80 commits were made, which is less than 5% of the total commits. Most of these commits may not be directly related to animation playback, seeking, applying transformations, or optimizing drawing. While these commits are undoubtedly important, it appears that the main bulk of the code is primarily maintained by bodymovin

Issues

Image description

On one hand, the maintainers of Lottie are doing an excellent job by closing numerous issues. However, it's worth noting that the presence of 600 open issues does indicate that there are ongoing challenges and areas that require attention. This suggests that there is still work to be done to address and resolve these outstanding issues.

Tests

As of my knowledge cutoff in May 2023, Lottie-web did not have any tests available on its GitHub repository. It is possible that the tests were implemented within Airbnb's internal codebase and not publicly visible. However, it's important to note that since I started writing this article, the maintainers have added some tests, which is a fortunate coincidence. This highlights the fact that contributing to Lottie's internals was challenging in the past due to the lack of validation mechanisms. It is encouraging to see that steps have been taken to address this issue with the inclusion of tests.

Features

Lottie-web does not support all features described in lottie format.

Image description

Different renderers with different problems

Lottie-web offers support for rendering animations to 2D canvas context, SVG, and HTML.


Each of these renderers presents its own set of challenges. For instance, when using the canvas renderer, it is possible to optimize animation playback by moving it to a web worker through the use of transferControlToOffscreen. This can significantly enhance the performance of the animation logic. However, SVG renderer does not support this capability since it is not possible to access the DOM from within a web worker.


On the other hand, the SVG renderer provides features such as hardware-accelerated blur filters, making updating SVG elements relatively fast. Canvas renderer, on the other hand, lacks built-in support for simple blur filters. Additionally, if an SVG contains numerous objects, it can impact performance. Therefore, when playing an animation, it is essential to strike a balance between performance and the desired features, taking into consideration the capabilities and limitations of each renderer.


You can find different articles about that for example


Or even service which optimizes your lottie,.and this, lol


It is indeed noticeable that many articles focus on optimizing Lottie files and their structure, employing various techniques. In some cases, these optimizations may necessitate modifications to the original After Effects file.


Readme says


More optimizations are on their way, but try not to use huge shapes in AE only to mask a small part of it.Too many nodes will also affect performance.

But why playing animation in web can be a problem?...

There is no renderer to webgl, webgpu

Imagine you're creating a 2D game using WebGL. It would be great to incorporate animations created in After Effects into your game. Although you can draw animations using the canvas element, it lacks certain features. Another option is to render animations as SVG, but to use them in the WebGL context, you need to serialize the SVG, convert it into an image, and then upload that image to WebGL. Unfortunately, at present, there is no ideal solution for this.


Actually, that's not entirely accurate. The lottie-weblibrary itself doesn't support rendering to WebGL. However, there is a package called canvaskit-wasm that wraps Skia (a graphics engine) with WebAssembly (wasm). This package includes a module called skottie which supports rendering animations into a WebGL surface. However, there is a drawback with this approach: using wasm requires loading a relatively large package, and it's uncertain whether all features are supported correctly, as the official compatibility table that tracks lottie support on different platforms does not include skottie.

History

Image description

I often wonder why this technology was only introduced in 2015. The concept seems quite straightforward, doesn't it? Well, perhaps not. My guess is that there weren't many developers with experience in this particular type of software. Anything related to graphics can often feel like a mysterious realm, almost like black magic, especially for someone like me.

What we should do as a community?

It's evident that the lottie-web project is in need of assistance.


How we can help?

  • Donate Contributing financially is one way to support the project. Even a small monthly donation can make a difference. Personally, I contribute $5 each month, and I hope that bodymovin considers the issues I've opened while enjoying a cup of coffee funded by my contribution.
  • contribute If you have the knowledge and skills, contributing to the project directly is another valuable way to assist. However, keep in mind that contributing can be challenging if you're not familiar with animation basics and the lottie format. That's precisely what this article aims to address!

Demystifying lottie


I did a workshop, where I tried to build lottie player from scratch.Another video can be interesting for you, it is about generating video, but the same time it overlaps with information I'm going to share.


Our plan:

  • Create simple animation in after effects
  • Export animation to json using bodymovin
  • Explore content of json file and it's structure
  • Create demo where we use lottie-web to render video
  • Replace lottie-web with our custom renderer to webgl
  • Compare our renderer design with design of lottie-web

Project in after effects

Let's create a simple animation in After Effects

  • Create project in after effects
  • Set up composition

A composition is a framework for a movie. Each composition has its own timeline. A typical composition includes multiple layers representing components such as video and audio footage items, animated text and vector graphics, still images, and lights

Image description

  • Create Solid layer

Here you can find an overview of different layers you can create with after affects.


Let's create just basic, solid color layer.

Image description


Here we can specify it's color, width and height. Image description

Ok, this is our animation:

Image description

But there is no animation!!!


Is animation the process of transforming something on a screen?What options do we have for modification?When you click on a solid layer, you can observe that it has the transform feature available.

Image description

You can change position, rotation, opacity, scale, anchor point of layer. Let's change someting:

Image description

Actually, this doesn't quite qualify as an animation since we're only altering the position for the initial frame.


Now, it's time to introduce keyframes:Let's shift our rectangle to the bottom right position.

Image description

What does this entail?For the initial keyframe at time (0), we have specified that the layer should be positioned at (0, 0).For the second keyframe at time (2s), we have declared the position as (1920, 1080). During the time interval between 0 and 2 seconds, the position will be interpolated using a linear function.


Now, how can we create a non-linear animation?To achieve that, we can utilize bezier curves. These curves, named after Pierre Bézier, allow for more complex and customizable animations. You can learn more about them here.


In this screencast, we have opened the curve editor and separated the curve for the position (we will delve into this topic in more detail later on).

Image description

Therefore, we have the ability to govern the changes in the x and y coordinates over time by employing a cubic bezier.

What exactly is a cubic bezier? You can find a comprehensive explanation on Wikipedia, which provides a valuable resource on this topic. Here's the link:

Image description

In order to define such a curve, we only need to specify two points, commonly referred to as in and out control points.


For instance, take a look at this curve It is composed of two control points:

  • The first control point has coordinates x=0, y=1.
  • The second control point has coordinates x=1, y=0. Image description


It's worth noting that we can even create a linear animation using a cubic bezier curve. Simply set the control points to be on the diagonal

Image description

In order to simplify our first animation, let's roll back to linear animation

Image description


Ok, lets store our animation to json, for that we use bodymovinFollow plugin installation steps

Image description

Lottie structure

Let's look at saved file:

{
    "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": []
}


It contains a lot of one-two symbol named fields. It's already zipped 😃!


In order to understand meaning of all fields we can use this lottie schemaAnother thing which can be convenient is json editorActually, lottie has very nice documentation, you can read it and skip this article 😄


Root level of lottie file called animation in specification.

Top level object, describing the animation


Let's focus on it

{
    "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 - version. It used for versioning data, according semver
  • fr - Framerate in frames per second
  • ip - "In Point", which frame the animation starts at (usually 0)
  • op - "Out Point", which frame the animation stops/loops at, which makes this the duration in frames when ip is 0"
  • w - Width of the animation
  • h - Height of the animation
  • nm - Name, as seen from editors and the like
  • ddd - Whether the animation has 3D layers
  • layers - Layers


So, I'm not going to copy paste the whole specification into this article, main idea, use it to understand content of lottie.


Some IDE support schemasfor example, in vscode you can add vscode/settings.json

{
    "json.schemas": [

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


And you will get description of of every property of your json right inside IDE. Image description

Everything clear with top level object.


What is inside layers?Just one solid layer:

{
    "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 - Layer Type, in our case it is 1, which means solid layer according schema
  • sw - With
  • sh - Heigh
  • sc - color, in out case it's #ef0c0c which is not exactly red! Ok, after effects, I got you.

Image description

Let's skip other properties for now, let's focus on important thing - ks


ks - is a Transform object which is responsible for animating this 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 - opacity
  • r - rotation
  • p - position
  • a - anchor point
  • s - scale


All properties which we saw in after effects transform object.


Every of them containsa property, which says is it animated property or not.If it's not an animated property it means there is no keyframes for that, value is static.


In our example only position is an animated property.If property is not animated k contains static value.


so, in our example

  • opacity = 100
  • rotation = 0
  • anchor point = [150, 150, 0]
  • scale = [100, 100, 100]


position is animated, so k contains 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
        ]
    }
]


it describes 2 key frames.


for t (time) = 0, s (state/value) is [0,0] means our layer in top-left corner of scenefor t (time) = 15.000000610962, (state/value) is [1920,1080] means our layer in bottom-right corner of scene


Pretty easy, right?


It's more complex than you think


First keyframe contains other properties: i, o, to, ti


What does it mean?


As I mentioned earlier, after effects support two different modes of position.


splitted and combined (this is not an after effect term).


with splitted mode x, y coordinates has separated keyframes definitions.


in this case we would have p (position) object like this:

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


s means splitted.


let's consider 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
        ]
    }
]


Everything is simple herefor t = 0 value = 0for t = 15.0000006109625, value = 1920

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


i - means in control point of cubic-beziero - means out control point of cubic-bezier


We've previously discussed the meaning of these terms.


When these values are used, the animation becomes linear.


Now, let's calculate the value for frameNumber = 7. Here's how it can be done.

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);


The same for y.Notice that the same logic of interpolation is applicable to opacity, rotation, scale and etc.


let's go back to our initial case with combined position


What is i, o, to, ti?


In this mode after effects exposes bezier curve which describes curve for position, and it's not the same as interpolation cubic bezier curve.


In transform.position interpolation settings we can select type of interpolation we want. Image description

It does not change the way how it will be exported to lottie, because, as I mentioned before, line can be represented with bezier curve too.


Let's consider this example: Image description

In exported json you can see that ti, and to describe in and out control points of bezier 2d path.

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


With this mode, ti, to describe only path, but how animation player should understand how fast we should move layer on that path?


Lottie stores that information inside i and o properties


In after effects it can be changed in keyframe velocity settings

Image description

We'll address the interpolation of position in this scenario later on.


Now that we understand the significance of all the key properties in our Lottie file, we have a complete grasp on the top-level object, layer storage, transform object functionality, and the organization of keyframes. We also know how to interpolate values using this data.


I believe we are prepared to build our custom Lottie renderer from the ground up.


Let's proceed with that in the next article.