Animations with JS

Bookmark

Creating different animation effects like bouncing, typing with vanilla javascript. Looking at several approaches of creating animations with time based functions and Request Animation frame.

by



Transcription


So how's everyone doing so far? Are you guys enjoying the talk? Nice. Any back-end developers here? Make some noise. Any motion designers? Okay, nobody. So today my topic is animations. Animations in simple word is just a way to communicate motion. So while this concept was introduced into movies first, before it was brought into web, but people had been trying to find ways to communicate motion from a very long time. How many of you remember this flip book? We would draw a bunch of drawings and flip them together really fast to create a perception of motion. So the idea is that when images are shown fast enough, our brain loses the ability to see individual frames and blends them together into a single moving image. But in digital world, we have frames. Each frame has a drawing, and the idea is similar to the flip book, is just put those frames together really fast to create the perception of motion. Hello, my name is Ashima. I'm a front-end engineer at Miro, and today I'm particularly going to be talking about animations in JavaScript. So all of you must have heard this term, frame rates, frames per second. I first heard about it in movies. And so frame rate is number of frames shown in one second. So if we show 60 frames in one second, it becomes 60 frames per second. And more the frames we show in one second, the smoother the animation gets. In order to render smooth animations on web, we target 60 frames per second. But that depends a lot on the device which you are using. For instance, if you're using a mobile phone, there is a high likely chance that it's running on a frequency lower than 60 hertz. So the frame rate would be like 30 frames per second or whatever the device supports. In web, we can achieve animations using JavaScript, using CSS, using CSS transitions and animations. In CSS transitions, you usually define two points. One is the starting point, and the other one is the ending point. Whatever happens in between is controlled by the browser. So this is what we call interpolation. In CSS transitions, it usually needs an action to trigger. So you would use a hover or a click, and you cannot run them in loop. So we have CSS animations. And in CSS animations, you can define intermediate points as well using keyframes, apart from just defining the starting and ending point. And yes, the interpolation, again, is controlled by the browser, how it would move between different states. But sometimes it's not just possible to do everything with CSS. I know the browser does a lot for us when it comes to animations. But when we need more control over the animations, so we need to use JavaScript. And so JavaScript allows you to control the interpolation and to simulate real world scenarios. For instance, free falling of an object or, like, falling off a snow. So let's suppose you are given this problem that you want to animate a box from 0 to 100 pixels. How many of you have encountered this in your interviews? I did. And I failed. But today we will see that there are a couple of� there can be a couple of approaches to solve this, basically. So you can use for loop if you are new to JavaScript. You can use timers, set intervals, set timeout. You can also use the animation frame and then the modern web animations API. So if you're using for loop, so you can run it from 0 to 100 pixels and tell your browser, okay, browser, render this to me at a particular position. But this does not work. What happens in this case is the box gets rendered directly at 100 pixels. But this is not what you want. You want to update the position incrementally. So the expectation is that browser will do something like this. Like show it at 0, 1, 2, and then 100. But the reality is that the line gets executed is translate X 100 pixels. Evil browser. So this is because how event loop works. I really like this diagram from Jake. If you haven't seen the talk, event loop. It's really a must watch. So what is happening here is that the for loop gets executed as a task. And after the entire for loop gets executed, browser goes to calculate the styles, render the layout, and then do the paint. And that is why we see the translate X 100 pixels only getting executed. Another way to solve this problem is using timers. You can use set interval and set timeout. Both of these functions take a callback and a delay time after which the callback would be executed. So what should be the delay time here? Now definitely you want to render a smooth animation. So for that, we discussed that we want to target 60 frames per second. So if 60 frames are to be rendered in 1,000 milliseconds, then one frame should be rendered in almost 16 milliseconds. So this is going to be the timer for the set interval as well as set timeout. So here, let's create a function like animate. And here you can use set interval. Inside set interval, you can update the position by one. And of course, you don't want to have the interval running infinitely. So you can clear the interval when the box reaches 100 pixels. And after that, the browser will render it at the particular position. Now, this works fine. If you run it, it perfectly runs a smooth animation. But the problem with set interval gets when the function inside set interval takes longer than the specified time. So let's suppose we had three functions here, right? And each one is to be executed at an interval of 16 milliseconds. But if the function one took longer than 16 milliseconds, let's suppose it took like 50 milliseconds, the rest of the two functions would be called instantly and in an unexpected order. So set interval does not guarantee the interval of execution. You can also approach this problem using set timeout. So here, instead of running a loop inside, instead of using set interval, what you can do is use set timeout in recursion. So basically, we are checking here if the position is less than 100, we will call the animate function again in the set timeout. So this works pretty well. And it also guarantees that our method gets fully executed. Until the function is running, the next function will never be sent for the execution. So the time difference between the execution of two functions is maintained. But there are still some problems with both the timers. So first of all, the delay which is provided in the second argument, it can be longer than intended. So there can be inconsistent timing between the frames. Also, these methods, they're not synced with the browser. So set interval, set timeout, they have no idea what else is going on the screen. So browser has to do the animation and then go back to paint the entire screen, which is a lot more computations. Also, if you use set interval, then the animations, even if it is running in the background, the animations still keep on running, even if the tab is inactive. So it's consuming more CPU and battery. To solve all these problems, there is request animation frame. So everybody knows, anybody knows how many of you are familiar with it? Okay. Almost everyone has heard. So request animation frame tells the browser that you want to perform an animation. And ask the browser, can you do this animation for me when you go to render the screen, when you go to paint? Browser is like, yeah, sure. I will do this. I will run this function for you whenever I go to paint the next time. So request animation frame takes a callback. As its execution is controlled by the browser, so there's no need to provide any timer, any delay time. So this is where request animation frame is in the event loop. So it's in some browser, it's here, like above the styles. But in some, it's here. But the main idea is the function inside RAF gets executed when browser goes to paint the screen. So the benefit is that it's synced with the browser. And also, it gets automatically adjusted based on the device refresh rate. So if you are running these animations even in your mobile phone, the RAF would automatically get adjusted to the frame rate of mobile phone. Also, like, when the tab is inactive, the animations don't run. So it saves a lot of CPU usage and battery life. Now if we have to rewrite this function using request animation frame, you can simply change the set timeout with request animation frame. And this would be, like, smooth animations. But can you see there is still some problem? So what is happening here right now is that this animation is running at different speed in different devices. Because the request animation frame would run at 60 frames per second in a browser. But in mobile phone, it would be capped at 30 frames per second. So in your browser, the animation is going to be two times faster. But you definitely don't want that. You want your animations to be consistent and smooth on all the devices. So here the idea is that instead of incrementing the position by one, what you can think of is that incrementing the position based on the time which has passed. So you can calculate here that what's the time has already passed and what should be the position of the box at that particular time. So we can pass a distance and a duration and increment the position based on the timing. Now this would run smooth on all the devices. So we have achieved a smooth and consistent animation on all the devices. But can you still optimize it further? If you see, there are two things which we are doing here. We are doing the calculations and we are doing the drawing. And both of them are running on the main thread. So the calculate so what is being done is that calculating position. The draw function, so right now what we want is to do the draw function in the request animation frame. So the responsibility of request animation frame should be only painting. And what you can do is offload the calculations to the worker. So we all know that workers run in a parallel thread. So instead of doing the calculations inside the main thread, you can offload that to worker. So this is how our calculate position looks. It's simply we are doing the set interval. In set interval, you can update the position and clear the interval when box reaches the hundred pixels. So now instead of calculating the position, what you can do is offload it to the worker. So instead of calling calculate position here, you can call calculate position in the worker. And in the worker, you can do all the same calculations. So the worker will send to the main file that this is the updated position. So in the main.js, here you can get the updated position. And this is how so basically the solution would be. So now what you have done is that offloaded the calculations to worker. So our main thread is free to take any other user action. So while the request animation frame is very powerful and it helps us achieve the smoother and performance animations, but there are still some drawbacks because request animation frame is running on main thread, right? And if the main thread is busy and the browser is taking longer time to paint the animations, then still we will have some struggle in having the smooth animations. It's like driving a Ferrari on Autobahn, but if you are stuck in a traffic jam, there is not much you can do. And also the request animation frame is capped at 60 frames per second in many Apple devices like iPad Pro. Although if you use CSS animations there, it can run at 120 frames per second. So there is another very powerful tool we have, which is web animations API. So this is very much inspired from CSS animations and transitions. Also syntactically it's pretty similar to the CSS. But the problem with CSS is that it's declarative. So whenever you are defining the key frames, you have to specify all the values while you're writing the CSS. But with JavaScript, it allows you to dynamically set the values from positions to durations. So if you have to animate this box using CSS, you would write something like this. Define the duration, timing function, and define the key frames. Web animations API have the similar structure. In web animations, you have the timing model and the animation model. In timing model, you would define durations, iterations, delay, easing. And in animation model, you would define the key frames. So this is how it looks in the CSS. And if you are using web animations, then the key frames would be passed inside an area. So this object looks like a key frame, apart from the fact that we are not defining the percentage here. So the solution with the request animation frame with� sorry. Web animations API would look something like this. So here you can leverage the dynamic nature of JavaScript. So what you can use is that a distance and duration is passed into the function. And you can dynamically set the distance. So I would say that we have many powerful tools to achieve animations on JavaScript. We have request animation frame. We also have CSS transitions and animations. And all of them are really powerful. And web animations is not something which is replacing them. But it's just an additional tool. So use whichever suits your requirement the best. Web animations API is also running on a compositor thread. So it's not running on the main thread. That is why you are able to achieve very performant animations with web animations API. However, the support is still limited. It's in Chrome and latest version of Safari and internet. But in the old browser, it's still not supported. Thank you so much. And that's all we have in terms of JavaScript animations. Thank you. I should get a seat in the front. That would be much better. So despite some technical issues, we managed. Congratulations. That was a wonderful talk. I'm wondering, just because we don't have a question right now, which is why this slide is up, people, please ask your questions using Slido. I wonder, do you have a portfolio site somewhere where we can see the animations you've made? The animations, I don't have it uploaded to GitHub at this moment. So I can share them later. Yeah. That would be awesome. I think everybody has seen your Twitter handle. So you can share the link there. Sure, sure. That would be really fun. One question I will take is how do you test with animations? We're used to simply turning them off in tests. But that introduces bugs in our animations and they go unnoticed, right? Yeah. So that depends on what kind of approach you are using. If you're using set interval or you are using with the request animation frame. I know there are a couple of problems with the testing framework. And since we are using the timers, it's also difficult to test it. But so far what I do is I use some customization, some libraries to do the testing. Cool. Thank you so much. Oh, we have one more question coming in, like, just in time. In which scenarios do you reach for CSS animations and in which should you use JavaScript animations instead? So if you know that the animation is going to be simple and you don't want much control over the animations, always prefer CSS animations. Because it's more performant. But if you need more control, like I told the interpolation, if you want to control those values as well, that you cannot define it on the CSS key frames, then it's better to go with the JavaScript. Awesome. Thank you so much. And thank you so much again for your talk. Give a big round of applause, everyone.
19 min
16 Jun, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career