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.
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.
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).
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.
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.