Pixels are expensive
How pixels get onto your users' screens is something you should know about. Not for the sake of knowing, but because in order to be effective as a modern web developer you're going to need to optimize for it.
The other day I was reading through What Every Frontend Developer Should Know About Webpage Rendering and I came away feeling like it missed out some crucial points. The article emphasizes CSS selector matching, layout (or reflow for Gecko-based browsers like Firefox), and being careful about forced synchronous layouts aka Layout Thrashing. All of these things, I should say, are good things for us to be aware of, but for me it doesnât cover everything we need to know about page rendering. For the most part weâll be trying to get to 60fps in our work, and that normally means understanding what the browserâs doing and optimizing for it.
I did a session at this yearâs Google I/O where I step through the pipeline in a fair amount of detail. If you prefer watching over reading then hereâs the session video:
The render pipeline part starts around 16 minutes in, in case you want to skip to it.
What Developers Need To Know About Webpage Rendering
From a developerâs point of view there are four major areas we care about:
1. Recalculate Style
This is the first part that the article looked at, and it pertains to selector matching and figuring out which styles apply to which parts of the DOM.
Normally, however, this is super fast unless you have a DOM tree with thousands upon thousands of elements and you change a large part of said tree, either directly or through cascade (like â say â by adding a class to the body that affects all its children).
For the most part optimizing selector matching is going to give minimal returns.
For the most part optimizing selector matching is going to give minimal returns. Not when you factor in what happens nextâŠ
2. Layout
Once we know what styles apply to the DOM we have a render tree, which is what we want to paint. To us it looks essentially like the DOM with things missing (the stuff we donât need to draw) and other things added (like pseudo elements).
If I change the dimensions of
<body>, then chances are the things inside of it will also need to change.
In layout we figure out the geometry of the page, i.e. where each element is on the page. This is where things can start to get a little more computationally expensive as the way elements are laid out is contingent; where one element lives is normally affected by another. If I change the dimensions of <body>, then chances are the things inside of it will also need to change.
Now this is also where things get interesting. Letâs say you change a layout property, something that alters the geometry of the page (paddings, margins, positions, text size, borders), what happens then? Well hereâs a video I made that shows the Chrome DevTools Timeline when you alter the geometry of an element. In this case I change its height:
The thing to notice is that the bars are shooting way past the 60fps line, and that we spend around ~23ms figuring out the geometry changes in each frame. This is true whether the animation is triggered by JavaScript or CSS as the layout work still needs to get done. When weâre aiming to achieve 60fps we have ~16ms to get everything done, and we just went over that in one task. Oops.
As something of a bonus point here, you definitely want to avoid layout thrashing, or forced synchronous layouts, which is where you cause a browser to do layout / reflows unnecessarily. Thatâs just going to make matters even worse.
With that said, however, from that video youâll hopefully also see that although we spend ~23ms in layout we spend a whopping ~600ms painting the changed pixels.
3. Paint
Now we know where the elements are we need to fill in their pixels, and this is where youâre going to spend the vast majority of your time. If you trigger paint youâre going to feel it. Well, your users definitely will when your site struggles along. Hereâs another sample where I cause paints (no layout changes here) by changing an element from gray to glorious hot pink:
The kinds of properties that trigger paint are anything âvisualâ like color, background or shadows. Thereâs a bunch of them, but the thing is if you change a layout property you are almost certainly going to trigger paint. If the geometry of the page changed, some of the pixels get dirtied and need to be repainted.
Once weâre done painting we are on to the final part.
4. Composite
We sometimes separate elements out into distinct layers, called compositor layers.
By default we paint elements into a single layer in memory, which for us as developers we can think of in a similar way to Photoshopâs canvas. If you change a pixel with a brush itâs destructive, you canât get back at the original pixels. (Thereâs an undo in Photoshop⊠L.O.L.)
To get around this problem, in a very similar way to Photoshop and other art packages, we sometimes separate elements out into distinct layers, called compositor layers. That way if we have to paint an element ideally we donât want to have to paint anything else. Iâve written about the rules that are currently in use for âpromotingâ an element to its own layer before, but stick around as thereâs a totally new way, which I will explain shortly! :-O
When weâre done with these layers we smush them all back together, which is compositing.
Avoiding performance bottlenecks
A while back I wrote an article with Paul Irish on HTML5 Rocks explaining how you can get high performance animations, or in this case, how you can exploit the pipeline to make sure you hit 60fps. What it boils down to is only changing properties that trigger compositing rather than layout or paint:
This is the list of the most common transforms (along with opacity) but in theory it should work for any of them. You will need to put elements onto their own compositor layer, though. The newest and shiniest way of doing that is will-change, which, for certain keywords, like opacity or transform will cause Chrome and Firefox to make the appropriate optimizations under the hood. In the case of these two keywords, for Chrome at least, means making compositor layers.
If youâre new to will-change, Sara Soueidan recently wrote up a post about it on dev.Opera. If you canât rely on that for your work, and that includes the current version of Chrome, then youâre talking old skool hacks like -webkit-backface-visibility: hidden or the perennial fave, -webkit-transform: translateZ(0); (ohai null transform layer hack!), and youâll be on your way again.
When we stick to compositor operations, we see super light frames and we get 60fps!
For the curious, the reason that we see transparent frames is because weâre still tracking to 60fps, even if the workload completes earlier than 16ms. In those cases we fill the Timeline bars with empty space. Ooh space!
Tools, Not Rules and a brighter future
Things are always changing, including the pipeline Iâve explained. The thoroughly spiffing news is that your developer tools will always be up-to-date and truthy, so if youâre not used to spending a lot of huggle time, all-up-n-cosy with your tools, change that. Theyâll love you back.
The really good news is that things are also getting much faster. At this yearâs Google I/O we showed fast, fluid, continous animations at 60fps in Chrome on Android, and thatâs something that continues to be really important to the Chrome team. I know itâs also really important for other browser vendors, too.
I hope to some day say that all you need to care about is writing âsaneâ code, whether it triggers layout, paint or anything else, and that you can let the browser do the work, but until then, this is what every frontend developer should know about webpage rendering.