A tutorial on how a browser turns bytes into pixels, starting from what a tokenizer is. Nine traced lessons — a script in the middle of the body stopping the parser, a stylesheet in the head holding first paint back to 448 ms, display:none and visibility:hidden differing by 6.7 ms of layout, a width change and a colour change and a transform re-running different stages, a read/write loop running 1,000 layouts instead of one, and a transform still moving at 60 fps through a three-second main-thread block — every number captured in Chrome 152 and shown with the trace it came from.
A frontend app built by Ananda Rizki. More of them at Labs.
The browser's rendering pipeline
Bytes in, pixels out — and the only question is which stages your change re-runs.
You have never told a browser where to put anything, in pixels, and you have never asked it to redraw. Both are being done for you, on a schedule you do not set, by a machine with about five stages in it. This page assumes nothing: no graphics, no computer science course, and none of the words on the rest of this tutorial.
WHAT THE BROWSER IS ACTUALLY BEING ASKED TO DO
You send text. The screen needs colours.
<h1>Ada</h1> — and has to end up with a decision about every pixel in a rectangle: this one is white, this one is black, this one is the grey at the edge of a letter. Nothing in the text says where anything goes or how big it is. Everything between those two things is what this page is about.And it has to do it again sixty times a second
So it is built as stages, and it skips the ones it can
Which makes one question worth everything
None of that is specific to the web. A game engine and a word processor have the same problem and solve it the same way; what is unusual here is that the document arrives over a network, a piece at a time, while the drawing is already happening.
WHAT YOU ACTUALLY CONTROL
You cannot ask for a frame
requestAnimationFrame callback is a passenger inside it, not the driver.Nothing you write applies immediately
el.style.width marks something dirty and returns. The work happens later, in the frame — which is why fifty changes in a row cost one layout rather than fifty, and why the one thing that breaks the batching is asking a question.Your code and the rendering share one thread
Two of the words here are not standards
Which is a short list, and it is the good news. There is one thing to learn to make a page feel fast, and it is which stages your change costs.
THE FIVE STAGES, AND WHAT EACH ONE IS FOR
01
Parse
Turn the HTML text into a tree of objects — the DOM — and the CSS text into the CSSOM. Two separate jobs, because they arrive as two separate files.
This is the only stage that runs once. Everything below it runs again and again for the life of the page.
02
Style
For every element, work out what every CSS property ends up being: which rules matched, which won, what was inherited, what the default was.
It stops exactly where geometry would start. The standards' own phrase: as far as possible without laying out the document.
03
Layout
Turn those values into geometry. Where is this box, how wide, how tall. width: 50% becomes 384 pixels here and not before.
One box's size moves the next one, so this is rarely scoped to the element you changed.
04
Paint
Work out how to draw each box: the text, the background, the border, the shadow. The output is a list of drawing commands, not pixels yet.
Turning the list into actual pixels is a separate step called raster, and it happens on other threads.
05
Composite
Stack the separately drawn pieces into the final image, in the right order, and move them about without redrawing them.
Most of this is on a second thread. That is why a page with a frozen script still scrolls.
Those five are the developer's version, and they are what Chrome's own documentation still uses. Chrome's engine actually has twelve — Animate, Style, Layout, Pre-paint, Scroll, Paint, Commit, Layerize, Raster, Activate, Aggregate, Draw — and the last six of those are what the single word “composite” is hiding. The machine view at 09 draws the nine that a page author can affect.
SO THE WHOLE SUBJECT IS ONE QUESTION
Given a change, which stages have to run again? A change to width invalidates geometry, so layout has to run, and then everything below it. A change to background-color cannot move anything, so layout is skipped and paint runs. A change to transform invalidates neither, so the compositor moves a picture it already has — and can do it on a thread your code is not holding.
Every lesson here is one consequence of that. A script in the middle of the body stops the first stage. A stylesheet in the head stops the whole pipeline reaching the screen. A loop that asks for a width drags layout into the middle of itself, once per iteration. And a blocked thread stops everything except the one stage that was never on it.
Next: the same thing stated compactly, as something to look a fact up in.
Simulator
The Rendering Pipeline
A first-principles tutorial on the browser's rendering pipeline. It opens by explaining what a browser is actually being asked to do — you hand it a string of bytes and it has to produce a rectangle of coloured pixels, sixty times a second — and works through the machine that does it: tokenizing HTML into a stream of tokens, building a DOM tree from them, parsing CSS into the CSSOM, combining the two into the tree that actually gets drawn, laying that tree out into boxes with real coordinates, painting those boxes into lists of drawing commands, and handing the result to a compositor that can move it around without asking the main thread. Then the question the whole subject turns on: when you change something, which of those stages has to run again? Nine lessons, each a short document or a short loop, traced frame by frame. A classic script in the middle of the body blocks the tokenizer while a deferred one does not; a stylesheet in the head does not block parsing but does block first paint, measured at 448 ms against a 12 ms control; a node with display:none is absent from the render tree while visibility:hidden is present and costs the same layout; a width change re-runs style, layout and paint, a background-color change re-runs style and paint but not layout, and a transform re-runs neither — 59 Layout events against 0 against 1, counted in a real Chrome trace; a loop that reads offsetWidth between writes runs 1,000 layouts and 224.5 ms where the batched version runs two and 0.8 ms; and a CSS transform animation keeps moving at 60 fps through a three-second main-thread block while the same motion written as `left` freezes solid. Every lesson also says why the pipeline is built that way, what the rule costs you in code you will actually write, defines every word it leans on where it uses it, and cites the specification clause or the captured trace behind each claim. Where a claim is Chrome-specific it says which Chrome; where the specification does not define a term that everybody uses — 'reflow' and 'layout' are both engine jargon, not spec vocabulary — it says that too.
Built by Ananda Rizki, a frontend developer — one of the small web apps he writes for fun at Labs.
More like this