Animations with JS
AI Generated Video Summary
So how's everyone doing so far? Are you guys enjoying the talk? Nice. Any back end developers here? Making 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 have been trying to find ways to communicate motion from a very long time. How many of you remember this flip book, where 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, it's just put those frames together really fast to create the perception of motion.
So if you're using For Loop, so you can run it from zero 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 would 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 function take a callback and a delay time after which the callback would be executed.
To achieve a smooth animation, we need to target 60 frames per second. The delay time for set interval and set timeout should be around 16 milliseconds per frame. However, set interval does not guarantee the interval of execution if a function takes longer than the specified time. Set timeout can be used in recursion to ensure proper execution timing. Both timers have issues with inconsistent timing and lack of synchronization with the browser. Additionally, set interval continues running even if the tab is inactive, consuming more CPU and battery. To address these problems, we can use request animation frame.
So what should be the delay time here? Now definitely, you want to render the 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 1. 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 1 took longer than 16 milliseconds, let's suppose it took like 50 milliseconds, the rest of the two functions would be called instantly 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.
4. Request Animation Frame and Consistency
Request animation frame syncs with the browser, automatically adjusts to the device refresh rate, and saves CPU usage and battery life. By incrementing the position based on time, animations can be consistent and smooth on all devices.
So anybody knows how many of you are familiar with it? Okay. Almost everyone has heard. So requestion 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 browsers, 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 the mobile phone. Also 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 the animation frame, you can simply change the set timeout with the animation frame. And this would be like smooth animation. 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.
5. Optimizing Animations and Web Animations API
To optimize animations further, offload calculations to a worker thread. Request Animation Frame is powerful but has drawbacks. Web Animations API is a powerful tool inspired by CSS animations and transitions.
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 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 100 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 performing 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 this 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's 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 it's declarative. So whenever you are defining the keyframes, you have to specify all the values while you're writing the CSS.
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 keyframes. So this is how it looks in the CSS. And if you are using web animations, then the keyframes would be inside an array. This object looks like a keyframe, apart from the fact that we are not defining the percentage here.
Q&A on Animations and Testing
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. But yeah, so I can share them later. Yeah, that'd be awesome. I think everybody has seen your Twitter handle, so you can share the link there. Yeah, sure, sure. That'd 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 are using setInterval or you are using with the requestAnimationFrame. 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.