I believe animation on the web is not only fun, but engaging in such a way that it has converted site visitors into customers. Think of the “Like” button on Twitter. When you “like” a tweet, tiny colorful bubbles spread around the heart button while it appears to morph into a circle around the button before settling into the final “liked” state, a red fill. It would be much less exciting if the heart just went from being outlined to filled. That excitement and satisfaction is a perfect example of how animation can be used to enhance user experience.
This article is going to introduce the concept of rendering Adobe After Effects animation on the web with Lottie, which can make advanced animations— like that Twitter button — achievable.

Bodymovin is a plugin for Adobe After Effects that exports animations as JSON, and Lottie is the library that renders them natively on mobile and on the web. It was created by Hernan Torrisi. If you’re thinking Oh, I don’t use After Effects, this article is probably not for me
, hold on just a moment. I don’t use After Effects either, but I’ve used Lottie in a project.
You don’t have to use Lottie to do animation on the web, of course. An alternative is to design animations from scratch. But that can be time-consuming, especially for the complex types of animations that Lottie is good at. Another alternative is using GIF animations, which are limitless in the types of animation they can display, but are typically double the size of the JSON files that Bodymovin produces.
So let’s jump into it and see how it works.
Get the JSON
To use Lottie, we need a JSON file containing the animation from After Effects. Luckily for us, Icons8 has a lot of free animated icons here in JSON, GIF, and After Effects formats.

Add the script to HTML
We also need to get the Bodymovin player’s JavaScript library in our HTML, and call its loadAnimation()
method. The fundamentals are demonstrated here:
<div id="icon-container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.7.4/lottie.min.js">
<script>
var animation = bodymovin.loadAnimation({
// animationData: { /* ... */ },
container: document.getElementById('icon-container'), // required
path: 'data.json', // required
renderer: 'svg', // required
loop: true, // optional
autoplay: true, // optional
name: "Demo Animation", // optional
});
</script>
Activate the animation
After the animation has loaded in the container, we can configure it to how we want it to be activated and what action should activate it with event listeners. Her are the properties we have to work with:
container
: the DOM element that the animation is loaded intopath
: the relative path of the JSON file that contains the animationrenderer
: the format of the animation, including SVG, canvas, and HTMLloop
: boolean to specify whether or not the animation should loopautoplay
: boolean to specify whether or not the animation should play as soon as it’s loadedname
: animation name for future referencing
Note in the earlier example that the animationData
property is commented out. It is mutually exclusive with the path
property and is an object that contains the exported animated data.
Let’s try an example
I’d like to demonstrate how to use Lottie with this animated play/pause control icon from Icons8:

The Bodymovin player library is statically hosted here and can be dropped into the HTML that way, but it is also available as a package:
npm install lottie-web ### or yarn add lottie-web
And then, in your HTML file, include the script from the dist
folder in the installed package. You could also import the library as a module from Skypack:
import lottieWeb from "https://cdn.skypack.dev/lottie-web";
For now, our pause button is in a loop and it also plays automatically:
Let’s change that so the animation is triggered by an action.
Animating on a trigger
If we turn autoplay
off, we get a static pause icon because that was how it was exported from After Effects.
But, worry not! Lottie provides some methods that can be applied to animation instances. That said, the documentation of the npm package is more comprehensive.
We need to do a couple things here:
- Make it show as the “play” state initially.
- Animate it to the “paused” state on click
- Animate between the two on subsequent clicks.
The goToAndStop(value, isFrame)
method is appropriate here. When the animation has loaded in the container, this method sets the animation to go to the provided value, then stop there. In this situation, we’d have to find the animation value when it’s at play and set it. The second parameter specifies whether the value provided is based on time or frame. It’s a boolean type and the default is false
(i.e., time-based value). Since we want to set the animation to the play frame, we set it to true
.
A time-based value sets the animation to a particular point in the timeline. For example, the time value at the beginning of the animation, when it’s paused, is 1
. However, a frame-based value sets the animation to a particular frame value. A frame, according to TechTerms, is an individual picture in a sequence of images. So, if I set the frame value of the animation to 5
, the animation goes to the fifth frame in the animation (the “sequence of images” in this situation).
After trying different values, I found out the animation plays from frame values 11 through 16. Hence, I chose 14 to be on the safe side.
Now we have to set the animation to change to pause when the user clicks it, and play when the user clicks it again. Next, we need the playSegments(segments, forceFlag)
method. The segments
parameter is an array type containing two numbers. The first and second numbers represent the first and last frame that the method should read, respectively. The forceFlag
is a boolean that indicates whether or not the method should be fired immediately. If set to false
, it will wait until the animation plays to the value specified as the first frame in the segments
array before it is triggered. If true
, it plays the segments immediately.
Here, I created a flag to indicate when to play the segments from play to pause, and from pause to play. I also set the forceFlag
boolean to true
because I want an immediate transition.
So there we have it! We rendered an animation from After Effects to the browser! Thanks Lottie!
Canvas?
I prefer to use SVG as my renderer because it supports scaling and I think it renders the sharpest animations. Canvas doesn’t render quite as nicely, and also doesn’t support scaling. However, if you want to use an existing canvas to render an animation, there are some extra things you’d have to do.
Doing more
Animation instances also have events that can also be used to configure how the animation should act.
For example, in the Pen below, I added two event listeners to the animation and set some text to be displayed when the events are fired.
All the events are available on the npm package’s docs. With that I say, go forth and render some amazing animations!
I’d seen Lottie used in a project on I worked on with a team by never checked it out.
This is a life saver.
Thanks for sharing.
You’re very much welcome.
Hello! I don’t understand what’s the purpose of this piece of code as there seems to be no difference between count === 1 and count >== 1 other than the plural word time/s.
if(count !== 1) {
document.body.lastChild.textContent = text;
} else document.body.appendChild(p);
Okay so when the animation has loaded, a
<p>
element is appended to the<body>
element (that is, added before the closing</body>
tag) through the DOMLoaded event.Now when the animation is played once, we want to display: ‘You have played the animation 1 time…’.
When it is played again, we want to update the number of times the animation has played (that is, ‘You have played the animation 2 times…’).
So what that conditional block does is to check if the
<p>
element that displays the animation count is present. If it is, the element’s content is updated. If it is not, a<p>
element is appended to the<body>
element.Now if that block wasn’t there, there’d be various
<p>
elements for the various number of times the animation has been played.I hope this is clear.
Thanks for the excellent article Idorenyin!
I am trying to get a “like button” lottie animation to display based on a css class it has. For example, on page load if it has a class “liked”, the lottie animation should show the last frame (a filled in red heart), and if it doesn’t have the class “liked” it should show the initial frame (just the outline of the heart). And when clicking, it should play the animation.
Any idea if this is possible?
Thanks!
Hi Drew,
I’d suggest you use the button’s click event handler and the window’s load event handler. Inside the load event handler, if the button has the ‘liked’ class present, you can display the liked frame with Lottie’s
goToAndStop()
method. And if it is not present, display the initial outline frame, like in the following:const button = document.querySelector('#like-button');
window.addEventListener('load', () => {
if(button.classList.contains('liked')) {
previouslyLoadedAnimation.goToAndStop(15, true); //assuming the 15th frame is the liked frame
} else {
previouslyLoadedAnimation.goToAndStop(0, true);
}
});
And then, in the button’s click event handler, check if the class is present. And then, play the expected animation using Lottie’s
playSegments()
method. The following is an example:button.addEventListener('click', () => {
if(button.classList.contains('liked')) {
previouslyLoadedAnimation.playSegments([15, 30], true);
button.classList.remove('liked');
} else {
previouslyLoadedAnimation.playSegments([0, 15], true);
button.classList.add('liked');
}
});
I hope this helps and I’m glad you like the article.
Thanks Idorenyin! I managed to get that working. The only issue seems to be with the lottie “container” param. It seems to only load the lottie on the first element it finds ( I did something like
`var button = document.querySelector(“.bodymovinanim”);
var buttonAnim = bodymovin.loadAnimation({
container: button,
renderer: “svg”,
loop: false,
autoplay: false,
path: “data.json”
});`
Not sure why it isn’t loading on all the other buttons on the same page. Each button uses the same class but has a different data attribute.
Hi Drew,
The
querySelector()
method returns the first matching element. What you want to use is thequerySelectorAll()
method. Then you’d have to load the animation in all the elements returned in that collection. This is what I’m talking about:const buttons = document.querySelectorAll('.bodymovinanim');
buttons.forEach((element) => {
const animation = bodymovin.loadAnimation({
container: element,
renderer: 'svg',
// the rest...
});
});
Hope this helps.
Brilliant! Thank you!
will these animations makes page rendering or performance slow?
There might be a delay when loading the animation due to getting the required JSON file. However, this is in the case whereby the JSON file is on another origin other than that of the webpage.
Also, if you use Skypack as opposed to npm, the performance delay due to yet another dependency is avoided.
Hope this helps!
css-tricks really is a life saviour! I didn’t find this onClick animation change for lottie animations anywhere! Thanks a lot :)
You’re welcome!
How to set the frame position of the lottie animation from the value we get from a range slider.
Like an animation is having 400 frames and a range slider has min:1 max:400, i want to get the value from the slider
and then display the corresponding frame.
For that, the appropriate Lottie method to use is the
goToAndStop(value, isFrame)
method.The
value
parameter will be the value gotten from the range slider while theisFrame
parameter is set to true since it’s the frame you want the animation to go to.Hope this helps!
hi i try to use your code from last section “Doing more” in asp.net default.aspx page and getting an error:
Uncaught SyntaxError: Cannot use import statement outside a module
on this line of code.
import lottieWeb from ‘https://cdn.skypack.dev/lottie-web’;
any suggestion
On the html file where the script element is, include the attribute,
type=“module”
, to the script opening tag.Hello,
I was trying to get an animation to play then pause at a specific time and then finish the animation upon a change of a variable from false to true.
I set isMarkerVisited to false then
if (isMarkerVisited === true) {animation.playSegements([37, 141], true)}
but when I try to change the variable to true in the console, nothing happens.