Ever had that moment when your page feels sluggish, animations stutter, or scrolling suddenly jerks? You open Chrome DevTools, hit the Performance tab, record a session , and bam , you see these colorful flame charts, with purple and green bars labeled “Paint” and “Layout.” But what exactly are these paint and layout events? How does DevTools even know when the browser is painting or recalculating layout? And how can you use this info to actually fix your UI’s jank?
I recently spent a few days chasing down a nasty rendering hiccup in a React app. The components looked fine, JS seemed fast enough, but scrolling on slower devices was choppy. Diving into DevTools’ rendering timeline was eye-opening , but only after I understood what’s going on under the hood.
Let me take you behind the scenes of how browsers detect paint and layout work, how DevTools hooks into this data, and how you can run smarter performance investigations on your web apps.
The moment your browser decides to layout and paint
Imagine your webpage is a living tree. When you change a style or DOM node, the browser has to figure out how that affects everything else , where things sit, how big they are, what color they have. That process has two big phases:
- Layout (reflow): The browser calculates geometry , sizes and positions , for elements on the page.
- Paint: The browser actually fills pixels on the screen, drawing backgrounds, text, borders, shadows, images.
Every time you tweak the DOM or CSS, the browser decides if it can skip these steps or if it needs to re-run layout or paint , which can be expensive.
For example, changing width on a div triggers layout because it affects size and position. Changing color triggers paint but not layout because it only changes appearance, not geometry.
How does the browser know when layout or paint is happening?
Under the hood, the rendering engine maintains an internal representation of the page called the render tree. This tree is separate from the DOM and CSSOM but derived from them.
When a change happens, the engine marks parts of the render tree as "dirty" , flagged for needing update. Then, during the rendering phase:
-
Layout phase: The engine walks the render tree and recalculates boxes’ sizes and positions. This recalculation is expensive because it can cascade , resizing one element can affect its children, siblings, and ancestors.
-
Paint phase: The engine walks the render tree again to create paint commands , instructions like "fill this rectangle with blue" or "draw this text" , and pushes them into layers.
-
Composite phase: The engine assembles these layers and sends them to the GPU for final display.
The browser’s rendering pipeline emits internal signals when each phase starts and ends.
Where DevTools gets its paint and layout data
Modern browsers expose hooks into this rendering pipeline for developer tools.
For example, Chromium-based browsers have a tracing system that records events during rendering. Some of these events are:
- Layout events: When layout starts and ends, with timestamps and sometimes affected nodes.
- Paint events: When paint steps occur, including which layers or regions are painted.
- Composite events: When layers are composed and sent to the GPU.
DevTools listens to these events via the tracing framework and maps them onto the flame chart you see.
This is why when you look at the Performance tab, you see purple bars for "Paint" and green bars for "Layout" , those represent actual recorded windows of time where the browser was busy with those tasks.
Why seeing paint and layout in DevTools matters
Every paint and layout costs CPU (and sometimes GPU) time. Excessive or unnecessary layout and paint cause jank , dropped frames, delayed input response, and that awful sluggish feeling.
By seeing exactly when and how long layout and paint take, you can:
- Spot expensive layout thrashing: lots of layouts triggered in quick succession, often by JS forcing synchronous layout reads.
- Identify large paint regions: painting big areas or complex effects like shadows or gradients that slow down rendering.
- Correlate layout and paint spikes with your JS or style changes to track down root causes.
A concrete example: chasing layout thrashing
I had a React app where an animation triggered a lot of style changes. The Performance tab showed a pattern of many tiny layout events stacked back-to-back. This told me something was forcing layout repeatedly.
Digging in, I found code calling element.offsetHeight inside a loop after updating styles. This forced the browser to synchronously calculate layout , which is slow because it can’t batch changes.
Removing that forced layout read and batching style updates reduced layout calls drastically, smoothing the animation.
Advanced debugging tips with DevTools paint and layout info
-
Use the "Rendering" tab: In DevTools, open "More tools" > "Rendering" and enable "Paint flashing." This highlights areas repainted each frame, so you can see if your animations repaint huge parts of the screen unnecessarily.
-
Layer borders: Also in Rendering, toggle "Layer borders" to visualize composited layers. This helps you understand if your page is creating many layers (which can be costly) or if elements that could be promoted to layers aren't.
-
Performance panel screenshots: Enable screenshots in Performance recordings to visually match what’s happening on screen during layout and paint spikes.
-
Use the "Layout Shift Regions" overlay: To detect unexpected layout shifts causing jank.
Under the hood: what triggers layout and paint events?
Some common triggers:
- Changing geometry-related CSS properties: width, height, margin, padding, position.
- Changing DOM structure: adding/removing elements.
- Accessing layout properties in JS:
offsetWidth, scrollHeight, etc. , these often force synchronous layout.
- Changing appearance-only CSS properties: color, background-color, box-shadow trigger paint but not layout.
Understanding which changes cause layout vs paint can guide you to write more efficient style and script code.
What DevTools can’t tell you (yet)
While DevTools surfaces a lot of paint and layout data, some details remain hidden:
- Exact invalidated regions during paint (beyond flashing overlays) are not fully exposed.
- How browser heuristics optimize layout calculations internally (like incremental vs full reflow) is mostly opaque.
- GPU timings and memory use per paint layer are still hard to access.
Still, the current transparency is a huge win compared to flying completely blind.
Wrapping up , how to use this knowledge
Next time your page feels janky, start by recording a Performance trace in DevTools and look for layout and paint bars.
Ask yourself:
- Are there frequent layout spikes? Could you batch style changes or avoid forced layout reads?
- Is paint happening on huge screen areas? Could you break complex visuals into smaller layers or simplify styles?
- Are composite layers behaving as expected? Could you promote important elements to their own layers using
will-change or transform: translateZ(0)?
This insight turns DevTools from a black box into a powerful microscope for rendering performance, helping you fix frustrating jank faster.
Happy debugging!