So there you are, at wits’ end, frustrated after hours of trying to find out why your beautifully crafted website is so slow to browse. You used the newest tech stack, the load time is shorter than for any page of that kind and the first meaningful paint is visible in the blink of an eye.
And yet, when you try to actually USE that website, it’s painful. And frustrating. “WHY?!” I’d bet my unfinished pack of nachos that in most cases the problem lies in the content reflows and repaints.
Reflows and repaints
Alright, so what exactly are reflows and repaints? To understand the concept fully, it’s best to start with a bit of a background on how the browser renders the page.
Layout
After the DOM (Document Object Model) is created and the styles are recalculated, the browser takes a moment to figure out how much space each visible HTML node is about to take and where it is going to be placed. This phase is called “Layout”, and at this point elements are only represented as vector boxes.
Paint
Once that part is done, the browser takes these vector boxes and rasterizes them (exchanges vectors to pixels) in a “Paint” step. The rasterized elements are put on “layers” (by default only one layer, unless there is a reason to move them away — more about that later).
Compositing
The layers are placed together and finally shown on the screen.
All of this work happens when we want to show just one frame to the user. But if any change is introduced to the interface (e.g. scrolling, triggering an animation), the browser needs to create a series of frames to represent that change.
When to expect Reflows and Repaints
Reflows happen when we introduce changes that force the browser to recalculate positions or geometry of elements — triggering the Layout, Paint and Compositing step. For example, Reflows can be forced by changing a display
property, appending an element to the document or animating the element’s size or position.
Repaints are introduced when our changes influence only Paint properties — both Paint and Compositing are bound to be triggered. We can see repaints for example when altering a background-color
or a box-shadow
property.
The important thing to know is that repaints only affect elements that are on the same layer as the changed node. We can take advantage of that and help the browser to find out which elements should be moved to their own layer by using a will-change
property or a translate3D hack in some browsers (there are also other situations when elements are “promoted” to their own layer, e.g. when we have a <canvas> or when the element is positioned on top of an existing layer due to the stacking context).
We should consider how big chunks of the page are affected by our reflows or repaints and, if applicable, try to scope them to smaller document parts using layers. This trick shouldn’t be overused — each layer consumes device’s memory. Too many of them be a cause of a browser crash.
Another things to note is that layers are the implementation feature in the most popular browsers. This means that we can’t assume that they are going to be in the browsers forever, they might get replaced or removed by the browser vendors.
At this point, for example in Chrome, the new layers are created while:
- Using 3D or perspective transforms properties
- Using animated 2D transforms or opacity properties
- An element is on top or a child of a compositing layer
- Using accelerated CSS filters
- Embedding <video>, <canvas>, plugins like Silverlight or Flash (in special cases)
As you’ve probably guessed by now, both reflows and repaints can be costly and we should avoid them if at all possible.
The only properties that can be animated or transitioned safely are the opacity
and transform
as they are added at the Compositing stage when all the layers are prepared. In many cases, we can stick to using these two properties for animation and avoid reflows and repaints altogether. If we really need to rerender content though, we should investigate how it affects the whole experience and if using separate layers can help us.
Layers panel
While spotting reflows’ damage is usually quite straightforward (like an appended element affects positions of other elements), guessing what part of a page was repainted may not be that obvious.
This might be a huge problem, as often Paint can be the most expensive task in the rendering pipeline. Fortunately, there is a tool that makes repaints spotting easier — Layers panel in Chrome Dev Tools. To reveal the panel you need to open a customisation menu in Chrome Dev Tools and in “More Tools” choose “Layers” option.
In the tab you will be able to see all layers currently existing within a website — they are represented as borders around elements or can be viewed in 3D mode, which also helps with understanding the stacking context of the page. If you interact with an element, the layers view will be updated to show you how your actions affected the website and which parts of the interface had to be repainted because of that change.
Another interesting feature in the Layers panel to note is that it offers detailed information about each of the existing layers. It might be helpful to understand why some elements get promoted as the new layers even if they weren’t meant to or how one element’s repaint can influence the following nodes.
Layers panel real life example
Unfortunately, the Layers tab is quite heavy and oftentimes I saw it crash during checking interactions. Nevertheless, even with its performance problems, it helped me discover some impossible bottlenecks that I would never been able to find otherwise. An interesting example could be this animation:
The animation can be triggered any moment, so the whole rating widget with stars (a fieldset
element) was already promoted to the new layer with a will-change: contents
property. It should be enough to avoid repaints outside of that area.
I always test my animations before shipping the code, so I decided to do it for this animation as well. I opened the Layers tab and checked one of the two last stars — no reflows happened. But then I tried to highlight the stars to the left and, to my surprise, the whole document below the rating widget got repainted. Reason? The stars weren’t above the “Heart rating” text in the stacking context. I changed the stars’ z-index
property and the repaints were gone (I double-checked that!).
This behaviour probably would never be found out if it weren’t for the Layers tab. Or until I discovered that my page was getting slower because of that widget.
Doesn’t it sound dreamy?
A couple things to be aware of if you want to to start using the Layers panel: It is quite heavy to use and it can crash websites. If your computer is not the strongest machine and it doesn’t have a lot of spare memory, using the Layers tab might be quite frustrating.
To be honest, I crashed my browser a few times trying to record the browser behaviour for this post (and lost quite a few moments cursing and trying to delete-and-then-restore the lost piece of writing). But I still recommend trying it out. On my work computer, the tab works like a charm and is one of my favourite tools.
If you aren’t lucky enough to have a good environment to use it, I recommend enabling the “Rendering” tab in Chrome (can be turned on the same way as Layers tab — in the “More tools” menu option). Rendering tab is not as precise as the Layers tab, but it is a nice fallback. It still gives you a possibility to see the Paint flashing and Layer borders (and some other cool things too). Actually I usually use both of these tabs along with the recordings from the “Performance” tab to fully understand how a website behaves.
If you made it to this point in the article, I think that you probably must be patient enough to test your page thoroughly. Now go and try out all the mentioned tools. I dare you to find out if your page can get any better to make your users happier. :)
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Try it for free.
Top comments (0)