Under the Hood
Rendering

Paint, layers, and compositing

With every box's geometry known, the browser still has to turn those boxes into actual pixels, decide which content deserves its own independent compositor layer, and assemble the result into the frame you see — three distinct stages this lesson frames as the last leg of the rendering pipeline.

Paint, layers, and compositing

Layout answered "how big is every box, and exactly where does it sit." It did not answer "what color is it," "what does its border look like," or "which box is drawn on top when two overlap." Those are separate questions, and the browser answers them in two more stages after layout finishes: paint, which turns each box into actual pixels, and compositing, which assembles those painted pieces — possibly spread across multiple independent surfaces — into the single frame that reaches the screen.

If you've read the layout, paint, composite lesson or the compositor thread lesson already, a lot of the mechanics here will be familiar — those lessons dissect which properties trigger which stage and how the compositor thread keeps animations smooth even when the main thread is busy, in real depth. This lesson has a narrower job: to slot paint and compositing into the rendering-engine pipeline this module has been building, right after layout and right where the flagship lesson left them. Think of it as the bridge between "how the render tree becomes geometry" and "how that geometry becomes the frame you already know from the animation and frontend modules."

Paint: the display list, in stacking order

Once a box has a real size and position, paint's job is to record every visual operation needed to draw it — fill the background, stroke the border, draw the box shadow, render the text glyphs, draw the image — as an ordered sequence of drawing commands usually called a display list. Nothing is rasterized to real pixels yet at the moment the list is built; it's closer to a recipe than a finished bitmap, and it gets executed (rasterized) either immediately or when the tile actually needs to be drawn.

The order those commands execute in is not the DOM order, and it's not arbitrary — it's the stacking order, and getting it wrong would mean elements drawing on top of things they should sit behind. A handful of things create a new stacking context, a self-contained unit whose children are stacked among themselves before that whole unit is placed relative to its siblings: a positioned element (relative, absolute, fixed, or sticky) with a z-index other than auto; any element with opacity less than 1; any element with a transform, filter, or will-change set to a property that would create one; and a few others. Inside a stacking context, z-index only orders elements against their stacking siblings — it can never reach outside to reorder against a different stacking context entirely, which is the usual explanation for "why doesn't my z-index: 9999 work."

/* Creates its own stacking context because opacity is less than 1 —
   this element's children stack among themselves, then the whole
   thing is placed as one unit relative to its siblings. */
.card {
  opacity: 0.98;
}

/* Also creates a stacking context, via transform rather than opacity —
   same consequence: z-index inside here can't escape to reorder
   against elements outside .card. */
.card-alt {
  transform: translateZ(0);
}

Layers: giving some content its own surface

Paint doesn't have to put every box's pixels into one shared surface. The browser can instead promote a piece of content onto its own compositor layer — its own bitmap, managed separately, that can later be moved, faded, or scaled independently without re-painting anything around it. A <video> or <canvas> element commonly gets one automatically, because its content changes on its own schedule and shouldn't force a repaint of the whole page around it. So does an element the browser expects to be animated via transform or opacity, and will-change: transform is the explicit way to request that promotion ahead of time rather than let the browser discover the need mid-animation.

/* Hints the browser to promote this element to its own compositor
   layer before the animation starts, so the one-time cost of
   allocating and painting that layer doesn't land on the first frame. */
.drawer {
  will-change: transform;
}

The result of deciding what gets its own layer is the layer tree — a structure sitting alongside the render tree and the layout geometry, recording which boxes share a painted surface and which stand alone. This is exactly the mechanism the compositor thread lesson goes deep on: why an animation limited to transform/opacity can be handed entirely to a separate thread once its target has its own layer, and why over-promoting content burns GPU memory for no benefit. Both of those — the what triggers a layer list in full and the cost of having too many — are covered there and in the layout, paint, composite lesson at the depth they deserve; this lesson only needs you to know that the option exists and where it sits in the pipeline.

Compositing: assembling the frame

Compositing is the final stage: take every layer — however many the browser decided to create — and assemble them into one image, in the right stacking order, at the right positions, with the right blending. On modern browsers this runs on the dedicated compositor thread, separate from the main thread that ran style, layout, and paint, which is precisely why a compositor-only animation can keep gliding even while the main thread is stuck running your JavaScript. Before final assembly, each layer is typically split into smaller tiles and rasterized — turned from display-list commands into actual pixel bitmaps — often on the GPU, which is what makes moving, scaling, and blending those tiles cheap enough to redo every frame.

That's the whole back half of the critical rendering path: paint decides what each box looks like, layerizing decides which boxes get independent surfaces, and compositing assembles those surfaces into the frame the display actually shows.

Where this goes next

Every stage covered so far — render tree, layout, paint, layers, composite — runs once to produce the first frame, and then some subset of it re-runs every single time the page changes afterward. Which subset depends entirely on what changed, and that's the question the capstone lesson answers directly: Reflow, repaint, and rendering performance ties this whole pipeline to the frame budget, the event loop, and the concrete techniques for triggering as little of it as possible.

Go deeper

Check yourself

Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.

  1. What is a display list, and why doesn't building one immediately produce pixels on screen?
  2. Name three things that create a new stacking context, and explain why z-index inside one stacking context can't reorder elements in a different one.
  3. What does it mean for an element to be promoted to its own compositor layer, and give two examples of what commonly triggers that promotion.
  4. What is the layer tree, and how does it relate to the render tree and the layout geometry from the previous lesson?
  5. Why does compositing run on a separate thread from paint and layout, and what does that separation buy an in-progress transform animation?
  6. In one sentence, what do rasterization and tiling actually do to a layer before it can be composited?
  7. Walk the full pipeline in order, from render tree to frame on screen, naming which stage produces the input the next one needs.