Lottie under the hood (part1)
Tags: lottie, lottieweb, javascript, animation
Published: 2023-06-14 05:54:12

Before you begin reading this article, please take 15 seconds to visit the [what is lottie?](https://lottiefiles.com/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](https://lottiefiles.com/what-is-lottie) is everywhere.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1l85uyzw2mpjj8wd481b.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xzqyojibp5zoxr6cyco.gif)


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

* [articles on dev.to](https://dev.to/search?q=lottie)
* [articles on medium](https://medium.com/search?q=lottie)
* [threads on reddit](https://www.reddit.com/search/?q=lottie-web)
* [articles on hackernoon](https://hackernoon.com/search?query=lottie)

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](https://lottiefiles.github.io/lottie-docs/schema/lottie.schema.json)

Recently, `LottieFiles` introduced a [plugin for figma](https://www.figma.com/community/plugin/809860933081065308/LottieFiles), 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](https://github.com/airbnb/lottie-web).


### Community
Lets look at list of top contributors:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5jw8v4qn8lmt8q7dgo5d.png)

- In the first place with 1602 contributions is [bodymovin](https://github.com/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](https://github.com/Hernan-Dev) It's likely the same individual.
- In the third place with 15 contributions is [knekne](https://github.com/knenkne)
who created a helpful introductory [video about using lottie on the web](https://www.youtube.com/watch?v=OSUH81OGuSg)

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](https://github.com/bodymovin)

### Issues

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b7iioaaa55my339snadq.png)

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 you started writing this article, the maintainers have added [some tests](https://github.com/airbnb/lottie-web/blob/master/test), 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](https://github.com/airbnb/lottie/blob/master/supported-features.md) described in `lottie` format.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9womxupp93lp2ofde0f.png)

### 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](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas). 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](https://jaredstanley.medium.com/improving-site-performance-by-optimizing-lottie-animations-9f032972d338)

Or even [service which optimizes your lottie](https://lottiefixer.com/),.
[and this](https://lottiefiles.com/features/optimize-lottie), 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](https://github.com/airbnb/lottie-web#performance)


> 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-web`
library itself doesn't support rendering to `WebGL`. However, there is a package called [canvaskit-wasm](https://www.npmjs.com/package/canvaskit-wasm) that wraps [Skia](https://skia.org) (a graphics engine) with WebAssembly (wasm). This package includes a module called [skottie](https://skia.org/docs/user/modules/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](https://github.com/airbnb/lottie/blob/master/supported-features.md) that tracks `lottie` support on different platforms does not include `skottie`.

### History

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbeybi7qgzxbk4ea4zjc.png)

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](https://opencollective.com/lottie) 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](https://github.com/bodymovin) considers the issues I've opened while enjoying a cup of coffee funded by my contribution.
* [contribute](https://github.com/airbnb/lottie-web)
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](https://holyjs.ru/en/talks/8b9ab68b933844869272de89633d0817/?referer=/archive/2023%20Spring/), where I tried to build lottie player from scratch.
[Another](https://www.youtube.com/watch?v=QAOaBpYLA58&t=1333s) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwrgoj1vgimjyusqn5as.png)

* Create Solid layer

[Here](https://helpx.adobe.com/ca/after-effects/using/creating-layers.html) you can find an overview of different layers you can create with after affects.

Let's create just basic, solid color layer.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x27s4yt0afywfkdybj6x.png)

Here we can specify it's color, width and height.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9z76m92rp3n6ptrrh9vq.png)

Ok, this is our animation:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2jp0dz45mvyqdi13cu2.gif)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8e5ntmq1kmbsof3xi4ai.png)

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9nkton0kv9t4lniq66u.gif)


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4f12be2cko4ow5lhc8gn.gif)

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](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxdxrnofxcrfxgzs1r2e.gif)


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](https://en.wikipedia.org/wiki/B%C3%A9zier_curve): 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dk25t4f8xrjwupjvwyh9.gif)

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](https://cubic-bezier.com/#0,1,1,0) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tqwos2iq33ui6pc8erq.png)

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](https://cubic-bezier.com/#.14,.14,.86,.86)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dulal7og4lv3avihajn9.png)

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uyxovsq44q8vdzq2obuh.gif)



Ok, lets store our animation to json, for that we use [bodymovin](https://aescripts.com/bodymovin/)
Follow [plugin installation steps](https://github.com/airbnb/lottie-web#plugin-installation)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/st1f9qbhe8k6wj14a1yg.gif)

### Lottie structure

Let's look at saved file:

```json
{
    "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 schema](https://lottiefiles.github.io/lottie-docs/schema/lottie.schema.json)
Another thing which can be convenient is [json editor](https://lottiefiles.github.io/lottie-docs/playground/json_editor/)
Actually, `lottie` has very nice [documentation](https://lottiefiles.github.io/lottie-docs/concepts/#general-concepts), 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

```json
{
    "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 schemas
for example, in `vscode` you can add [vscode/settings.json](https://code.visualstudio.com/docs/languages/json)

```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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zq7k1u9j8dyv3ckphgi3.png)

Everything clear with top level object.

What is inside [layers](https://lottiefiles.github.io/lottie-docs/layers/)?
Just one solid layer:

```json
{
    "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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbtkv9hjgjubrvkvyf9w.png)

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`.

```json
{
    "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 contains 

`a` 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`.


```json
[
    {
        "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 scene
for `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:

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

`s` means splitted.

let's consider `p.x` (position.x)

```json
[
    {
        "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 here
for `t` = 0 value = 0
for `t` = 15.0000006109625, value = 1920

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

`i` - means in control point of cubic-bezier
`o` - 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.

```js
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cibeaw68gkxnn977bjl5.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nwf2dl8f1wi4cqlevht.gif)

In exported `json`

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

```json
{
    "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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/utricl44g6c0li76n3zw.png)


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.