Video Summary and Transcription
In this Talk, the speaker discusses how to enhance animation in React apps and optimize animation in browsers. They explain the browser's animation pipeline and the importance of avoiding frame drops. The speaker compares CSS and JavaScript animations, highlighting the benefits of using the requestAnimationFrame API. They also discuss combining CSS and JavaScript animations using the Web Animation API. The Talk concludes with tips on avoiding redundant calculations and provides additional resources for further learning.
1. Introduction to React Animation
Welcome to React Summit. In this session, we'll explore how to enhance animation in React apps. I'm Nikhil, a software engineer at Boosman, passionate about design systems, performance, and React. Connect with me on GitHub and Twitter. Let's get started!
Hey everyone, welcome back to another session of React Summit. And in this session we'll be talking about how we can make our animation game in React apps to the next level by animating React with Finesse.
So before diving in, here's a little bit about me. I'm Nikhil and I'm a software engineer here at Boosman. I love to talk about design systems, performance, and React in general.
So if you're also a front-end geek like me, I have left my GitHub and my Twitter handles here in the slides. So feel free to connect, it would be a great chat. And other than this, I love giving talks and also I love to travel, I love to play sports, especially cricket and table tennis. I have been learning table tennis specially for quite some time and it's super fun thing. So yeah, it's going to be a good chat. So would love to connect with all of us, so that's it.
2. Understanding Animation in Browsers
An animation is everything a user does in an app. Browsers create frames to analyze user interactions. Frame drops occur when the browser's 16.7-millisecond window is compromised. The browser goes through code, generates frames, calculates styles, determines layout, and then paints everything in pixels.
All right, so with this, let's start with our talk with a very simple statement that what exactly is an animation, right? The sentence actually answers it all. Everything that you do in an app as a user is basically an animation.
Now, you might be thinking that, oh, isn't a floating button or properties like transform, et cetera, or some carousel appearing, are all of these things animations and not other stuff? So basically, what browser understands is whatever you as a user do, be it like scrolling or you're clicking on a button or you're hovering on a button, even if it doesn't show any animation, everything is actually an animation to a browser, right?
And if you want to visualize it in your mind, it's basically like a flipbook like a browser also maintains. So each and every time you're scrolling or you're interacting with your website, your browser actually creates a set of frames for you to actually analyze what's actually happening and it just runs them all together like a flipbook sort of thing, so that you get an idea that, oh, it's an animation of things actually been moving.
Now, where the problem actually lies is when the frames that actually the browser has, if this frame cycle gets messed up in some time, that's where actually our jank happens or when the performance of animations actually drops. Now, if you try to understand what actually is a frame drop or why the frames actually drop when we're animating.
It's basically because each and every time your browser has a window of 16.7 milliseconds, how that math comes is basically what you see on the screen right here. Now, if you want to achieve 60 frames per second and you have thousand milliseconds, if you divide them both, you'll get like 60, roughly 16.7 milliseconds. So your browser actually has 16.7 milliseconds to generate each and every frame.
Now, what it actually needs to do when there is basically during this particular set of time. Right. So basically, let's take an example. I have written this two or three lines of JavaScript code here, which actually is creating, basically adding a class, which is my special box to my box. Now, let's see how the browser actually goes through and scans all of your code. So first, it actually figures out, oh, now something has changed. It encountered some JavaScript and it says, oh, I need to generate another frame. And now the 16 millisecond time starts for the browser.
Now, the next thing the browser analyzes is, oh, OK, there is some style change that is also been there in this JavaScript file, which is I'm adding some classes and I need to also figure out what these classes are doing, what's basically sort of style recalculation do I need to do. Right. So that would be a second step. So in the third step, now the browser figures out that, OK, I've calculated the styles, I have run the JavaScript. Now I need to figure out how do I render all of these boxes on the screen. Right. For this, the browser needs to know the layout. Right. What's going to be the dimensions of each of the divs that you have and how it has to be positioned in the screen. Right. So this is like that layout phase where the browser is actually trying to do those calculations.
In the fourth step, when it is finished, it tries to decide that, OK, now I have figured out the layout. I need to paint everything in in terms of pixels and for painting.
3. Optimizing Animation in Browsers
The browser creates bitmap images of elements and combines them in the paint phase. There are five phases in the pipeline: JavaScript evaluation, style calculation, layout, painting, and composition. Understanding the pipeline is essential for optimizing animations. Dropping frames causes jank, so it's important to learn techniques to optimize animations, starting with avoiding style recalculation by using opacity instead of display none.
What the browser does is it actually creates like starts painting your elements in multiple layers and it actually creates a bitmap out of each layer and just throws them out as pixels on the screen, which is the paint phase. Right. And in the very end, it just tries to combine all of these bitmap images that it had created and does some styling on top of that. Like if there are different layers, like opacity is like a very different layer on top of it. Does it need to reorder the layers in every point of time in the users are interacting with the application. Right.
So these are five phases. If you want a very concise gist of that, it's basically JavaScript evaluation that we just talked about. Second, the style calculation basically reflows, which is, you know, reflows in the sense. That's what our styles basically changed in primarily. Third being the layout for being paid and the fifth being last and not the least composition phase. Right. So this is actually what our pipeline actually looks like. Right.
Now, the thing that you will be thinking of is why do we need to know all of this? Right. What's the significance of it? Not the reason behind it is like, as I mentioned earlier, once this pipeline is optimized, Then only then can you can get optimized animations, be it React, be it JavaScript or like be it any other place that you are actually adding animations. Right.
Because whenever you are dropping frames, your browser has to keep up with the frames that are being generated. Right. It has to probably discard the frames that were late and it has to keep on matching the recent frames. And so it has to like skip some frames and like go ahead. And which is why you see actually a jank. So that is actually what the root cause is. So let's try to get some techniques on how we can optimize them, basically. All right.
So starting with some simpler ones, which is how we can avoid the style recalculation phase, right. Which is what we saw. So if you had seen in the pipeline, the browser has to recalculate some styles, right? So what we can do is try to minimize that effort of the browser, which is what we try to do in the reflow phase, which is avoid those properties which actually cause the browser to make the calculations a lot. For example, if I tell you, if you use a display none, right, that element has to be totally been removed from your document, right? Which means it's its dimensions have to be recalculated. And also the other elements that are the neighbors of it, how do they have to be positioned again? So actually, it's calculating everything once again, right? Instead, if it's possible, it's better to use opacity rather than that.
4. Advanced Techniques for Avoiding Reflows
To avoid reflow calculations, use CSS properties that are less paint expensive. You can also use CSS to indicate elements that are more paint expensive and require recalculations. However, use this property sparingly for better performance.
There's also like a whole list of other properties that can help you with avoiding these reflow calculations. But this is one of the examples. This is what we can avoid these reflows by using CSS properties that are less paint expensive because the composition phase is actually a lot more cheaper than the paint, right?
Coming to the next one is how we can take this part to the next level, which is you can tell any element via CSS, which is will change auto that, hey, this is one day. Well, this is an element in my, in my app, which is a bit more paint expensive. And it just gives a hint to the browser that like, please do some recalculations or like just take care of this particular element because it might require some some resource resources of yours and some pain calculations are like can be heavy here. This property again, has to be just sprinkled across applications. It's not very advised to use it too much regressively, but definitely at some places it gives you some benefits of performance.
5. JavaScript Animations and Performance
JavaScript animations can't be done using CSS alone. The set interval method, although it seems convenient, has performance bottlenecks. It runs on the main thread without considering its load. Also, it doesn't guarantee consistent function calls at the start of each frame, leading to frame losses. To avoid these issues, the better approach is to use request animation frame, which ensures smoother animations.
All right, coming to the next step, which is JavaScript animations, which we do a lot in our day to day lives. Right. So if I tell you like, if you had to animate something, just by JS, you can't use CSS. What would your very first thinking would be without any thought? You would say, yeah, we would probably use set interval. I would be, yeah, that's fine. But in interval, although it looks like it can solve your purpose, but it comes with its own performance bottlenecks as well. Let's see how.
So in here, you'll see this example where I have a use effect. And in this, I'm trying to calculate, I have a set interval in place. And if you see, I'm running it for approximately 16 milliseconds on the very bottom, which is roughly 16 FPS that we saw in our previous slide. Right. And we're trying to calculate our position of an element again and again. And we're trying to update the style of transform to this particular position that we've calculated. And we're doing this every 60, 16 milliseconds to be precise. Right. Now, what's the problem with this? Why I'm saying that it's not good. So the point that it brings is first thing it does not guarantee. If you're using set interval, obviously, it's running on your main thread, which means it doesn't take into account if your main thread is busy or not. Every 16 milliseconds, it's going to do these operations, putting a lot of load there. And the second thing is, if you imagine this picture here and you have frames that a browser knows that, oh, I have to do evaluations, style recalculation, layout, composition, everything in that 16 millisecond timeline. Because it doesn't guarantee your function call to be at the very start of your frame when you are ready to print. It often happens that sometimes your pipeline is bound to slip your frame time. And this is exactly where frame losses and the junk happens. So that is why set interval is not good. So which is a better approach? It's somewhat similar because you're here missing out this frame, you know, this frame wagons. So if you see this previous slide, this is actually like you if you see this JavaScript and your style recalculation, it's actually a wagon. Your frame is actually like a wagon frame. So while you are missing it, as I mentioned, you are having janks appearing on your animations, which is where request animation is coming into picture. So the better approach here becomes request animation frame. Now, the difference that it has, as you would observe, is the code that we are writing here is actually the similar one.
6. Request Animation Frame for Smoother Animations
The request animation frame API guarantees that animations start at the very beginning of each frame when the browser is ready to paint. It ensures smoother animations and better performance compared to the set interval approach. Using code examples, we can see how the animations with the request animation frame are more stable and consistent.
The only difference is now we are actually using a request animation frame API, which is just recursively, recursively running our animate function here. Right now, let's see how it actually becomes better. Now, you would have immediately seen the difference in the screenshot that you have on the right to the previous one. Now, it actually guarantees you that the pixel pipeline calculation that it is involved, it makes sure that the relative functions are actually starting at the very start of whenever the browser is ready to paint. And on top of that, it adds an image and it adds another optimization that request animation frames always run whenever the browser is actually ready to paint rather than just as we saw in set interval.
For it doesn't run that for every 16 milliseconds. It finds the time whenever the browser has the time to paint. And it applies all these automations, like if you have a tab, if you like change the tab or even if you close the browser, right. It tries to stop your optimization. It tries to stop your animation, which is where it becomes like, you know, more performant than the set interval approach.
Now, let's see some code in action. So I have these two code sandboxes. One being the example of set interval that we had just seen and the other being the example of the same one regarding the request animation frame. Now, I've opened these two code sandboxes side by side. And let's see how both of them react to an FPS meter that we have added here. So this is the example of set interval. Right. And if I try to, you know, do some scrolling behaviors here, right. If you see here, it actually, you know, just there is like some frame jitter that actually happens. Right. It's not always stabilized as 120 FPS, which is fine for this example. But like it can become more and more complex as your animations become complex.
Whereas on the second example. So here we have request animation frame running in. And even if I try to do a scroll, it's not that much affecting my main thread or like or my frame rate. Right, you know, if you if you see it's actually not even going at one one nine FPS, it stays super consistent at 120, which is a lot more better.
7. Choosing Between CSS and JavaScript Animations
Request animation ensures consistent frame rate and better performance. CSS and JavaScript both have pros and cons. CSS is hardware accelerated and browser controlled, while JavaScript allows for more fine-grained control. Combining CSS and JavaScript using the Web Animation API provides similar performance with less noticeable differences.
And even if I try to do a scroll, it's not that much affecting my main thread or like or my frame rate. Right. You know, if you if you see it's actually not even going at one one nine FPS, it stays super consistent at 120, which is a lot more better. Right. In terms of performance. So this is where request animation, you know, make sure that you don't miss your frame wagons as we used to do in the set interval approach. Right. So now your JavaScript calculation has a lot of time to do these calculations and make it to the 16 millisecond timeline. All right.
Now, another question that might arise in your mind is, should we use CSS or should we use JavaScript? Should we use both or should we use neither? Or like neither? Won't be an option. Sort of. So should we use either of them? Now, both of them come with their pros and cons. Right. So if you think about CSS, it's hardware accelerated. It runs on a very separate thread that's called a compositor thread. What it means is it can use another thread besides the main thread, which uses other resources like GPU, which are like not blocking your main thread or your CPU. Right. Which is a lot more performant. And it's also browser controlled. Right. Because your browser cannot decide whether it can like what sort of optimizations out of the box can it can it add. So CSS has those advantages, but some disadvantages that sometimes people find is there are some more complex animations that maybe real world products require, which is where some more fine grained control gets to be required, which is where JavaScript animations prove to be people's choice.
Now, how about like you could combine both of them. So like you can use another API that JavaScript now exposes, which is Web Animation API. So if you see on the left and the right, CSS is using keyframes, whereas you can write this exact similar code using the animate function that you see on the right side using the Web Animation API. And you can just pass on these same styles that you have been giving to the keyframes as an array of objects to your Web Animation API. You can configure it and it actually gives you a similar sort of a performance if you talk about like, it doesn't give you that much of a jank. So it's somewhat similar. CSS still proves out to be a little more better, but it's very less noticeable, I would say.
8. Combining CSS and JavaScript for Animations
CSS animations can be combined with JavaScript using the Web Animation API. There are libraries available for optimized animations, such as frame of motion, motion one, and react spring. Efficiency is doing things right, while effectiveness is doing the right things. The intersection observer API is a better approach for checking if an element is in the viewport compared to using the get bounding client rect function.
CSS still proves out to be a little more better, but it's very less noticeable, I would say. To see this in action again. Let's see. So this is your CSS animation using the animate function. This is sorry, your animate functions, Web Animation API. And other example that I have is using another JavaScript animation that is our plain old set interval. So if I again open them side by side, open this one as well, and try to see how these are responding, open the FPS meter again. So you'll see again, you have 120 FPS here. And in a similar fashion, if I open the FPS meter again here, you again are having like sort of 120, like in here it does. But this is because this is a JavaScript example. So this is actually a lot more worse than using the same JavaScript code, but this time using a Web Animation API to do stuff. So it actually gives you the feel as if you were just using CSS, right? Which is why you can choose between whether you want to use JavaScript or CSS, or can you combine both using Web Animation, right? This will be your go to.
Now, this can also be taken like on a very different step where if you don't want to go into all of these animations, there are already a set of libraries that have been well battle tested against all of these wild wild case scenarios. And they give you all such performance optimizations out of the box. Like you can try frame of motion, you can try motion one, or you can try react spring, there are like hundreds of libraries that are already there. And they're also very decorative, very simple to use. I've already shared this example here, where you can just have, for example, for a day, you have motion or Dave, you can give your animation sequences to it, you can configure it. And it's, it's also very simple as well to do. All right, now, with efficiency, there also comes effectiveness, right? So this is a very famous quote of Pete Drucker that I saw. And it's it's very significant to what we are discussing here. So like it says efficiency is doing things right. And effectiveness is doing the right things. So how can we like make sure that we do right things and by right thing, what I mean is can we like reduce some more redundant stuff in our animations, right? So let's try to think more about that.
Now there's another battle of get bounding client direct function and the intersection observer API. So a lot of times if you're not using any other intersection observer APIs, and you have an animation that actually depends upon making sure if your element is available in the viewport or not, you might be using the get bounding client right and trying to figure out by all sorts of calculations if it's there or not, if it's there or not by so many event listeners, this can be like a lot more messy and also performance wise not good, right? Which is where the intersection observer API comes into picture. Like if you see on the right side, I have this use effect. And it just gives you entry. It is intersecting key here, which with which you can easily tell if your element is already there in the report or not. And you can do some stuff on that. So it's implementation wise performance wise, a lot more better approach to do.
9. Avoiding Redundant Calculations and Recap
Avoid redundant recalculations by storing values as variables. Implement bouncing and throttling for smoother animations. Recap of discussed topics: understanding the pixel pipeline, optimizing CSS styles and JavaScript animations, combining CSS and web APIs. Additional resources and demo links are provided for further learning.
On the second phase of avoiding redundant stuff is how we can try to avoid recalculations whenever we whenever it's needed or whenever we can do it. For example, if you have some items and in here if you see, I am trying to add a transform style and in here if you see this line container dot get bounding client, it will run for each other every item inside the loop, right? Which is like not going to change that much for each and every item if you see here, which is where you can just get that out of your loop. And each and every time we can just refer it as a variable and not calculating it again and again, which is a very small thing, but it sometimes proves very effective. All right. And one of the other things is, as you already know, we have been doing it a lot, but this is also being applied to animations as well, which is the bouncing and throttling, right? So if you have an animation, it depends on scrolling and you you can also try out to debounce your animation in such a way that you can only or like, you know, throttle that in a sort of a particular time interval and not give calls a hundreds of thousands of times, which might not even be needed. So you can produce a function calls in those cases. All right. So with this, you might be thinking that, yes, now we are animation, animation gurus and we now know a lot more about how we can make animations fairness. But obviously you think there's like reading every time is always not enough. There's also a bunch of things out there that you learn with practice by going through the code and getting experience. So there's also a lot of things that needs to be covered. But here I just added a quick recap here of what we have discussed just to make sure we have learned everything and you just get a quick glimpse of what we did. So first, I think we talked about how the pixel, what the pixel pipeline is and how actually it refers to frame rates and how messing up this pixel pipeline is going to mess up your frame rates and eventually your animations. Right. And next thing being how we can try to reduce browser's work by using lighter CSS styles like opacity and stuff, which are not computation heavy, how we can optimize JavaScript animations using request animation frames and not use set interval that much. And how we can use CSS animations plus the web APIs and how we can combine them both together and how we can actually upend stuff like that. Right. So that's actually what we discussed. Here's some that I had read while I was also preparing for the talk. So they contain a lot more information about making animations more performant. So don't forget to check them out as well and just give them a read. I think after this presentation, it would be like a quicker read for you to just grasp what's happening. And also attach a few demo links that we just saw in our in our talk here. So just for us to go through the code and refer to it as we are learning. Alright. So with this, I would like to end my talk here. Thanks again to the team here and React Summit for having me and also for you folks are being such patient listeners. Hope you had learned something valuable today. So thanks for having me. Cheers.
Comments