 I counted 2044 fish in my demos, so if you like fish, then you are really at the right talk. So again, my name is Will, and I'm going to talk about silky smooth animation with CSS. But actually, before I talk about silky smooth animation, I want to take just a moment to talk about the exact opposite. I want to talk about janky animation, often just called jank. So whenever you have animation that sort of hiccups or stutters along, those are examples of jank. And jank is not a good thing. Simply put, jank just doesn't look good, right? And in some cases, it can even be distracting. Or in a worst case scenario, it can even be disorienting. Motion, when applied carefully and thoughtfully, can be a huge asset to your UI. But if you have janky motion, then that just sort of ruins things. So we don't like jank. We want to do everything we can to eliminate jank so that we are left with silky smooth animation. Now to ensure smooth animation, you have a goal of 60 frames per second. This means that while your browser is animating something, it is doing all the calculations that it needs to do. It's figuring out what pixels to update. And it's pushing those updated pixels to the screen. And it's doing all of this 60 times in a second. This works out to an average budget of about 16.7 milliseconds per frame that your browser has to work with. Now 16.7 milliseconds is a very tight deadline. And it can potentially be a very ambitious one, too, because your browser may be asked to do a lot of work in that short period of time. And if your browser cannot complete all of that work, then your frame rate may suffer, which means that your animations will be just that much rougher. So I actually want to give you a quick tour now of this work that your browser is doing behind the scenes. And I think this will be great, because this was really helpful for me, because when you understand the work that your browser is doing, then you can start to see ways to optimize things, to make things more efficient. Because anything you can do to decrease the workload for your browser means that your browser can finish that work just that much faster. And your frame rates will stay up, and your animations will stay smooth. Now this sequence of events I'm going to show you is mostly the same across all major modern browsers, but there are some differences. So this particular sequence I'm showing you is going to be Chrome centric. So I will try to call out the Chrome specific parts as I come across them, though. So when you think about all the work your browser has to do from when it receives the HTML, all the way to the very end where it actually pushes pixels onto the screen, conceptually you can divide the sequence of work into four main stages. Loading, rendering, painting, and displaying. So starting with loading, your browser receives the HTML, and it parses this HTML to figure out what elements it's working with. Once it knows the elements that it has, then it can arrange these elements as nodes into the DOM tree or the document object model tree. This is a representation of all of the elements that are found within the document. From here, your browser can transition into the rendering stage. Here, your browser will use the DOM tree as a basis to create another tree called the render tree. Or if you're Firefox, you'll call this the frame tree, but they're the same thing. So the render tree cares specifically about the visual aspects of elements that will ultimately be displayed on the web page. So in order to figure out how to craft the render tree, your browser needs to calculate styles. So it's going to take a look at the CSS. It's going to determine which style rules affect which elements. Also, if you have any animations that are in progress, then it is going to calculate the values for that moment in time for any animated properties. So with all of this visual information, your browser can start to shape the render tree. So for example, all browsers by default say that the head and title element should not be displayed on a web page. So assuming you don't muck around with that default, then those elements will not be included in the render tree. Also, let's say that you have CSS that says that all h1 elements should have display none on them. In that case, h1 would also not be in this render tree. But the render tree doesn't, it's not just about removing nodes that were in the DOM tree, it can also have nodes added to it. So for example, if we have a p tag in the HTML that has a couple of lines of text in it, those lines of text are actually treated as separate text nodes and each of those text nodes are added to the render tree. And just one more example, if you have CSS that defines before or after pseudo elements, those would also be nodes that are added to the render tree. So we have this render tree that your browser has crafted and now your browser is going to start thinking about layouts. So it's going to calculate geometry. It's going to look at all of the elements that are in the render tree and figure out their widths, their heights. It's going to take into account margin and padding and figure out the positions of things on the page. And it's very common for the geometry of one element to affect the geometry of another element. For example, the height of an element can affect the vertical position of an element after it. So there's a bit of interplay here between the elements in terms of geometry and layout can be very costly for your browser to do. So just keep that in mind because we'll be talking about layout again later, but layout can be very expensive. After rendering, we move on to the painting stage and here Chrome will assemble a series of draw calls. You can think of draw calls as instruction sets for how to turn an element into pixels. So when these draw calls are executed, your browser will actually draw pixels onto a bitmap. And this process is called rasterization. Now it's important to note that these pixels are not on the screen yet. They have only been drawn into bitmaps that are hanging out in memory right now. There's actually one more step to actually get these pixels onto the screen. But before I talk about that last step, I want to kind of take a step back here for a moment and talk about where we are from a broader perspective. So everything I've shown you so far has occurred within the renderer process. The renderer process has multiple threads running within it. Threads are your browser's way of splitting up the work that it has to do and potentially doing that work simultaneously across threads. So everything I've shown you so far has happened on the main thread except for rasterization. There are other tasks that are also on the main thread though. JavaScript is a big one. The main thread is typically your source of a performance bottleneck, simply because there is so much that's going on on the main thread. And then next here I have raster threads. So Chrome will actually offload rasterization to these raster threads. So it's very common to have multiple raster threads running at the same time, all of them working to draw pixels. And then the last thread I have here is the compositor thread. You can think of the compositor thread as the dispatcher. So it will assign work to be started on other threads. And it will also do a bit of coordination between these various threads. So at this point I want to introduce you to the GPU process. The GPU process has one thread in it, the GPU thread. And that is where your GPU or your graphics processing unit is going to do the work of actually putting pixels on the screen. So here we are now in the final displaying stage. So earlier that rasterization that your browser did, those bitmaps are going to be uploaded into the GPU thread where they are treated as textures. And your GPU is very adept at putting textures onto the screen. So it's going to do that. And finally, success, we have pixels pushed to the screen. So this is great. But there's good news and there's bad news. So the bad news is that I actually skipped a lot of stuff. There's actually more work that your browser has to do to push pixels around. And there's even more work on top of that, not directly related to pushing pixels around, but it's still work that your browser has to do. A big one that I barely mentioned is JavaScript. JavaScript can have a huge impact on the workload your browser is taking on. So that's the bad news. Now let's talk about the good news. So again here I have the four stages that we work through, loading, rendering, painting, displaying. And your browser does not need to do all of this work again every time for every single frame. In fact, when it comes to CSS animations, once your browser has gone through the loading stage, it does not need to reload anything in order to support CSS animations. So we can just kind of forget about loading for now. It is very common, however, to have CSS animations that force your browser to redo work from rendering, painting, and displaying. But if you are very careful with how you craft your animations and you only use certain CSS properties, then you can eliminate the work from rendering and painting such that you're only asking your browser to redo a little bit of work in the displaying stage. And that's good, because as I mentioned before, the less work your browser has to do, the smoother your animations will be. So at this point I want to talk about three categories of CSS properties and the costs associated with animating them. The first category of CSS properties are those that cause reflows. So this happens whenever you animate something that changes geometry. So when you animate the geometry of an element, you are forcing your browser to redo layout. If you recall from before, layout can be very expensive, because not only does your browser have to recalculate layouts and the geometry for the element you're animating, but it could very well have a chain effect forcing it to redo layout for all the elements below it in the render tree. So that can be very expensive. The second category is CSS properties that when animated will cause repaints. So this is whenever you are forcing your browser to redraw pixels for an element. So the most likely culprits here are color and background color. Now repaints will not cause reflows, but reflows can often cause repaints. The reason being is if you force a reflow and an element is moved, your browser may have to repaint it in a new location. So reflows and repaints are not that great, but this third category, this is great. So these properties, when animated, will cause no reflows and no repaints. In fact, these animations can be handled completely within the GPU thread, which makes them extremely efficient. What's happening when you animate these properties is your browser will actually find the element being animated and promote it into what is called a compositor layer. Compositor layer is very easy for your GPU to animate around independently of anything else that's on the page. So it looks like it's very limiting here. We only have three CSS properties, but you can actually do a lot with these properties. Transform will let you move things around a page, or scale things, rotate things, skew things if you want to. Filter lets you pull off a lot of really interesting effects. So you can change the color of your element by adjusting the hue, or you can turn it into grayscale, or you can bump up the contrast or the saturation. There are two effects, however, blur and drop shadow that are not GPU accelerated. If you animate these, then you will cause repaints. So just be aware of that. And then finally, I have opacity, which lets you fade things in and out. So at this point, I want to start to get into some demos. So I'm going to show you some demos of fish swimming around. Each of these fish is a single div. So I have a div with a before and after pseudo element, and I'm using these together to draw a fish. Essentially, all you need to know is that I'm animating divs around a web page. In this first demo, I'm going to be animating using a keyframes animation. So these will be absolutely positioned fish, and I'm going to be animating the left CSS property to move them back and forth. And then I'm going to have a demo right after that that has the exact same setup, but instead of animating left, I'm going to animate transform. So I'll be using translate X to move them back and forth. So let's dive in here, and I am offline, but it's okay. Wait, wait, wait. It's okay. All right. So 20 fish swimming around. So these are the fish that are being animated via the left CSS property. These are very talented fish. They can swim both forwards and backwards. So it looks like they're having just a fine time, but let's actually take a closer look. Oh, I'm using jQuery, which is online. Okay, hold on. Was it box model? I should have kept jQuery local. Which one? This one? All right, we're saved. Man, I'm glad, because my talk is like two-thirds demos. I would have just have to get off stage right then, but... Okay, so we've got our 20 fish swimming around. I've opened my dev tools here. I'm going to hit escape, and down here under rendering, I'm going to turn on paint flashing. And this is going to show green rectangles around everything that has been repainted. So you can see that there is a green rectangle around all of these fish. And more importantly, it is a persistent green rectangle. So these fish are actually being repainted over and over and over every frame. So that's not the greatest. That's a lot of extra repaints that the browser is doing. But let's move on for now. So I'm going to close this, and up here on the timeline tab, I'm going to record a two-second timeline. And this is going to show me all of the activity that happened on the page during that two seconds. So you can see we have our threads here. Here's the main thread. And here we have four raster threads going at the same time. And then finally, the GPU thread here at the bottom. And each of these colored bars is showing when the browser had to do work. So we can actually zoom in all the way down to the frame level here. And we can see exactly when and in which thread the browser is doing work. And we can even zoom in further and see exactly what work is being done. So that's pretty cool, but I'm going to stick to this bird's eye view for now. Come down here to the call tree tab. And this is going to show me an aggregate of the work that the browser is doing for particular types of tasks. So here you can see that rendering is 73.3% of the work being done. I know it's kind of hard for you guys to read from there, but 73.3% of the work being done in terms of time. So I can actually drill into that and see that Recalculate Styles is 38.9% of the work being done. And that seems pretty high. So what's actually happening is because we are animating via left, which is not a GPU accelerated animation, this is running on the main thread, which means on the main thread we have to calculate a new value for the left property every single frame in order to create this animation for a fish. And we're doing that for every fish, so we're doing that times 20. So that's a good bit of work there. And then let's see. Down here we have layout 14.6%. And painting seems to be 13.4% of the work done. So let's compare these fish to these fish. So again, I have 20 fish swimming around, but this time I'm using transform, which is GPU accelerated. So again, I'm going to turn on paint flashing and nothing has happened because there are no repaints. So the browser does not need to do any repaints to support animations via transform. Below paint flashing there's this option here to show layer borders. So it's kind of hard to see out there, but there is now an orange rectangle around every fish. So these orange rectangles show any elements that have been promoted into a compositor layer. If you recall from before, compositor layers are very efficient for your GPU to animate around. So that's looking good so far. And now finally let's do a timeline recording once again. And you can see it's a lot sparser. In fact, we're not even being shown the main thread or any of the raster threads. And that is because there is simply no work being done in those threads. The only work the browser has to do is in the GPU thread. So that's a big improvement. Let me hop back into my presentation here. And now having shown you these two demos, you might be thinking, well, Will, I don't know. Looks like those fish were swimming just fine in both of those demos. And to that I would say that you are absolutely correct. To be honest, this MacBook Pro is going to have no problems animating 20 small fish around a screen, even if I am using terrible CSS. So let's push things a little further. How about instead of 20 fish swimming politely back and forth, I have 1,000 fish freaking out. So before I do that, I'm going to open up my DevTools real quick. Let's make this smaller. And down here, I'm going to turn on the FPS meter. And this little meter in the top right, this is going to show me the frame rate that Chrome is delivering while animations are occurring. So let's see what this looks like. So here we have 1,000 fish spilling onto the screen. These fish are being animated via the left and top CSS properties, which, as you may recall, cause reflows and repaints. These are not GPU accelerated. And you can see kind of choppy waters here. And right now Chrome is telling me that my frame rate is right around 15 frames per second, which is actually pretty bad. So let's see if we can get something better than that. So here I have 1,000 fish being animated via transform, which is GPU accelerated. So you can see it's noticeably smoother. And right now Chrome is telling me that my frame rate is a rock solid 60 frames per second. So we just achieved a frame rate increase of 45 frames per second, which is absolutely massive. So by using the right CSS properties to animate on, we can achieve a huge performance boost. Now 1,000 fish swimming around on a screen is obviously a completely contrived scenario. You will probably never have to animate fish like this. More likely you'll be animating a side panel or a modal dialogue or navigation menu or any of these sorts of things. I'm just using fish as an example. And also in this last demo I was animating 1,000 fish simply because I'm trying to push the hardware that I'm on. I'm on a relatively powerful MacBook Pro, but you cannot always assume that your users are going to be on high-end devices. And furthermore, you cannot always assume that there won't be other things fighting for the CPU. Your browser may very well be carrying out a very intensive task at the same time that you're asking it to support your animations. So with that in mind, I have an example from personal experience. Earlier this year I was working on a side project to create a JavaScript-powered web app that would run on a Raspberry Pi. Now Raspberry Pies are fantastic little devices, but they're not super powerful. And to make matters worse, this JavaScript web app I was writing had very, very intensive client-side JavaScript that was eating up a lot of the already limited CPU. So for this thing, I had a UI with a progress bar going along the top, and I was animating this progress bar via width, which causes reflows, causes repaints. It's not the most efficient thing to animate on, and the results were absolutely terrible. So the progress bar was just super, super janky. So I had to try a different approach, and I'm going to show you what I came up with. So on the left is the original approach, very straightforward, just animating width from 0 to 100%. And on the right is eventually what worked for me. So instead of animating width, I am animating a transform, and I'm actually just scaling this div along the x-axis to make it grow and shrink. And this was great, much better results, because by using transform, I now have a GPU accelerated animation, which is a lot better. So by using CSS properties animated in maybe slightly different ways, you can achieve the same end results but get a lot better performance. One more example I have of this. Here I have two buttons, and they're just color shifting from gray to pink. And on the left is maybe the most straightforward thing you would think of just animating the background color between gray and pink, but animating background color causes repaints. So another option is, what I have on the right here is to use CSS filters. And you can use a combination of gray scale and brightness to achieve the exact same color change, except now you have a GPU accelerated animation. So I've talked a lot about having better animations from the standpoint of having a better frame rate. But there are also other benefits to using these GPU accelerated animations. So I'm going to show two of these bonus effects right now. So here I have the next to two last fish in my demo. So I have two fish swimming ever so slightly back and forth. And the fish on the left is being animated via the left CSS property. And I'm going to zoom way in. Hold on, let me make this bigger. So I'm going to zoom way in here down to the pixel level. And you can see that as this tail is moving back and forth, it is snapping to pixel boundaries, right? So that's kind of choppy at the pixel level. Now compare that to animating on transform. So you can see it's not snapping to the pixel level. It's actually smoothly anti-aliasing across pixel boundaries. So it looks a lot smoother, a lot nicer at the pixel level. And then here I have my last demo, and this one's my favorite. So I've kind of hinted at this before. You guys see what's coming. So GPU accelerated animations can run completely within the GPU thread. And that's what the fish on the right has. It's being animated via transform. Other animations like top and left cause repaints and reflows. They are animated on the main thread. And JavaScript also runs on the main thread. So what happens when I have absolutely terrible JavaScript that completely wrecks the main thread? And we're going to find out. So I'm going to click this button, and it's going to cause awful JavaScript to run. This JavaScript does nothing but churn non-stop in a loop for 10 straight seconds. And I'm going to click this button, and the fish on the left is dead. RIP left fish. But as you see, let me click this button again. So the fish on the right keeps on chugging along because that animation is completely inside of the GPU thread. It's not affected by whatever chaos is happening on the main thread. So I think that is so cool, because you could have just really, really intensive JavaScript that's just ruining everything. But your animation can still be perfectly fine. So that was my last demo. Winding things down here. I have one last major point I want to make, and that is that all of this is under constant development. These are constantly being ironed out. Browser performance is constantly being increased, and new debugging tools are constantly being made available. I actually had a whole segment for this talk planned where I was going to talk through a pretty gnarly text anti-aliasing issue with animations in Safari. But while I was working on this talk, Safari fixed it. So I had to take it out of the talk, but that's great, right? Things are always getting better. And as an example of new debugging tools coming down the pipeline, so this is actually already available in Chrome Canary, but it is the ability to throttle the CPU that your browser is using. So this is excellent and very handy for emulating and testing what your animations would look like on lower-powered devices. So I think this is going to be super useful, and hopefully it's landing in Chrome proper very soon. All of the demos I've shown you are available online at animation-workshop.herokuapp.com. And if you go to this link, then you can also see a link to a GitHub repo that has all of this demo code available. And that's all I've got. Thank you. Come on over this way. Whoa. There we go. Is it better now? Oh, that was, like, so much stuff. A lot of fish. A lot of fish, too. How did you get into, like, understanding animation at such a deep level? Is this something that came about through your work as a UX engineer at par.dot, or is this just your, like, I need to figure this out? Well, it's something that I had an interest in. I like to do a lot of CSS animations. I think there's kind of this subculture on CodePen where you try to do as much as you can without JavaScript. So you would use all sorts of crazy CSS animations to pull things off. And I kind of got sucked into that. It was kind of fun. And so that's kind of where I started, you know, learning more about CSS animations. And then I pitched the idea for this talk. And then I realized that there's so, so much more below the surface. And as I mentioned in my talk, I actually skipped a lot of steps. There's a lot of work that goes into this. So, yeah, browser animation is really, really impressive. Yeah. It's very, very difficult, too. So folks in the audience are curious if you can speak to making, like, the frame rate performance for non-GPU accelerated animations. Is there any way to keep, like, a 60 FPS when you're animating other properties? Or is it difficult? Right. You can do things to improve non-GPU accelerated animations. There are certain techniques and things that you can recognize that are expensive. Like, I believe background fixed is particularly expensive. That makes scrolling very expensive because it causes a repaint on that entire region that is being scrolled. If you have a lot of performance issues, sometimes you can cut out some of the niceties. Like block shadow, for example, might be expensive for lower-powered devices to animate. So maybe you can cut those back for the sake of performance. So, yes, there certainly are things that you can do beyond the GPU. Has striving to keep your animations performant and really focusing on animating properties that are GPU accelerated? Has this helped you, like, think differently about your work? I thought it was really neat with your two examples where you're like, here's another way of thinking about this. I'll just move the div instead of width. Right. So I have had some experiences at work where one particular example was, for a past job, I was working on kind of an online photo gallery viewer, which seems not that bad, but we had basically an app that was infinite scroll for literally tens of thousands of images. And so that's where we started seeing all kinds of performance issues, including animation performance issues. So, yeah, it's been really helpful to kind of to understand, like, how the browser handles these things and what things are expensive to the browser so that I can make things in my work more performant. That's great. Do you, like, do you see a world where... Well, I guess my question is, I'm curious, like, how these properties get defined. Like, which one is non-GPU accelerated and which one isn't? And do you... Is there, like, in your studies when you found this out, is there a world where some of those may change? Like, browsers could actually say, you know what, I'm going to improve the animation of this property so that it is more performant. Right? Well, I think it mostly comes from the capabilities of the GPU. Like, there are certain things, like, for example, if you have an animation that causes text to reflow, I don't know what you can do about that. You're going to have to, you know, draw those words in their new reflow positions. But there are some things that, you know, the GPU can very easily animate. Like, just moving around textures on the screen is very easy. I remember correctly, CSS filters did not use to be GPU accelerated. I don't know if anyone can back me up. But as browsers progressed, then they were added as something that can be GPU accelerated. So it's possible that browser vendors may start moving more properties over or... Right. I don't think everything can be GPU accelerated, but I do think that more things very likely could be. Great. Well, thank you so much. That was a wonderful talk. And I know that you recently said you just started speaking, but this was incredible, and I hope that you continue along through this informative talk. Thanks, everyone.