How to avoid unnecessary repaints
Martin Hofmann
September 20, 2018
I was working as a front-end developer for one of our customers when I had the above conversation with a member of my team. At that time we were basically asked to analyze the web app and come up with ideas on how to improve UI performance. As a start I did what any web developer would do nowadays: I asked Google. But I only found a few helpful pieces of information on the topic. Some – actually most – of the explanations to our animation problem were rather unspecific and seemed mysterious to me. I quickly realized that this can be caused by a variety of things, including a combination of certain widely-used HTML, CSS and JavaScript patterns which don’t cause issues when used alone but can lead to trouble when combined. One could say that my starting point was not really defined, and as a result I was not able to find a straight-forward solution. I decided to figure out what’s going on in the browser and continue from there.
The first step was simply to use the Paint flashing option in Chrome’s DevTools. So I inspected our suspicious janky animation and found out something curious. Even though the animation should only affect a specific area, it – for whatever reason – caused full (!) page repaints. It took me less than two minutes to find other elements on our site showing similar behavior, like these:
Examples of full page repaints when the animation is triggered.
So far, so bad. I just wanted to tweak an animation a bit but instead found a wide-reaching issue in our web app. The browser is doing unnecessary work. It is repainting content it is not supposed to. So, what can I do next with my newly discovered knowledge?
In order to find out why the browser behaves like this, I learned a lot about the browser’s rendering pipeline. There are various good resources out there. It was probably the first time I fully understood how a browser generates pixels from code. This diagram shows three different paths the browser can take to render elements to the screen.
After finding out about the third option, the idea of avoiding any layout and painting steps really made sense to me. I checked the code of my animation. Here's a simplified version of it:
I started optimizing things one by one.
First, I changed the animated property from margin-left: -300px to transform: translateX(-300px). And this already solved my problem. The animation was not janky anymore. Yeah 🎉 🎉 🎉! I don’t know if the FPS meter would show 60 frames per second now, but you could definitely feel the difference – even on an up-to-date laptop.
So, what’s going on? By using the transform property, we’re allowing the browser to skip the layout and painting step. It just moves around the already painted layers, which is called compositing. This technique is commonly used in cartoons in order to avoid repainting parts of the image that do not change:
Compositing was invented by Walt Disney in the 1950s to speed up the production of cartoons.
Pushing it further
Well, I could have stopped at this point, the animation was pretty smooth. But the paint flashing tool still displayed green rectangles across the whole page. I wanted to push it further.
The next step was to restrict the transition property to only animate the transform property instead of just specifying all. Unfortunately, this didn’t bring any benefit. It was just like before. Nevertheless, I think it is better to restrict it just in case another developer needs to change code here and accidentally animates other properties.
I did some further research and found out about the will-change property. It can improve the performance of CSS animations, but shouldn’t be spread across the whole codebase as it also decreases the performance when used too often. A side navigation is a pretty good use-case, I guess, because we know that it will be animated frequently. Therefore I added it.
Boom! The paint flashing was gone completely. This means that we’re only performing compositing now instead of doing repaints all over the place. The will-change property basically moves the whole element to its own compositing layer so it can be animated without touching other elements. Success! Job done, time to go home 🏠.
Nah, I’m still here for a conclusion, of course…
Conclusion
I learned a lot about the browser and highly recommend the resources linked in the blog post. As a web developer it’s very important to know about the rendering pipeline. A profound knowledge about this topic allows you to find good solutions for a variety of problems without having the feeling of looking for a needle in a haystack. And of course, you can impress your colleagues by explaining all the technical charts within Chrome’s DevTools to them, haha.
By the way
The web site featured in this blog post is SevenCooks, an alternative platform for vegetarian and vegan food. We at Peerigon are trying to make sure every visitor has a rich user experience. You can explore recipes using filters and collect them in your personal profile. Sorry, German language only!
Thanks to Leonhard Melzer and Tanner Hoisington.
Web Development
CSS Animation
Rendering
Browsers
Devtools
Read also
Francesca, Ricarda, 11/21/2024
Top 10 Mistakes to Avoid When Building a Digital Product
MVP development
UX/UI design
product vision
agile process
user engagement
product development
Leonhard, 10/22/2024
Strategies to Quickly Explore a New Codebase
Web App Development
Consulting
Audit
Leonhard, 07/15/2024
User Input Considered Harmful
TypeScript
Web App Development
Best Practices
Full-Stack
Validation