Reading the Paint Profiler in DevTools
Part of Paint Invalidation & Repaint Boundaries in Performance Budgeting & GPU Architecture.
The problem
A card’s hover animation shows Paint events of four milliseconds in the Performance panel. The card animates transform and opacity only, so the paint must come from something else — but the trace says “Paint, 4.1ms” and nothing more. Guessing which of the card’s shadow, gradient, rounded corners, image or text is responsible means removing them one at a time and re-measuring.
The paint profiler removes the guessing: it records the individual draw commands a Paint event performed and how long each took.
Root cause analysis: a Paint event is a list of commands
When the browser paints, it produces a display list — a sequence of drawing commands such as “draw rounded rectangle”, “draw text run”, “draw image”, “draw shadow with blur radius 40” — and then rasterises them. The Performance panel’s Paint event shows the total time; the paint profiler shows the commands.
It must be enabled before recording. Advanced paint instrumentation is off by default because it makes traces larger and slower. It is a checkbox in the Performance panel’s settings.
The profiler shows per-command timing. Selecting a Paint event and opening the Paint Profiler tab lists the commands with their durations and lets you step through them visually, seeing the canvas build up. The expensive ones are usually few: a large blurred shadow, an image drawn at a size that needs resampling, a long text run, or a filter.
Paint flashing shows where, the profiler shows what. The Rendering drawer’s paint flashing highlights repainted regions, which identifies the element; the profiler explains the cost inside it. Used together they answer both questions for an animation that should not be painting at all.
Layerisation matters for interpretation. Paint work is attributed to the layer being painted. An element promoted to its own layer paints alone; an element sharing a layer paints alongside everything else in it, which is why a small hover effect can show a large Paint event.
Step-by-step resolution
Production code pattern
/* Before: the profiler showed one command taking most of the paint time —
a large blurred shadow redrawn on every hover frame. */
.card-old {
box-shadow: 0 18px 40px -12px rgb(16 12 32 / 0.45);
transition: box-shadow 200ms ease-out, translate 200ms ease-out;
}
.card-old:hover {
box-shadow: 0 28px 60px -12px rgb(16 12 32 / 0.55); /* re-blurred every frame */
translate: 0 -4px;
}
/* After: the shadow is painted once on a pseudo-element and cross-faded. */
.card {
position: relative;
isolation: isolate;
transition: translate 200ms ease-out;
}
.card::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
box-shadow: 0 28px 60px -12px rgb(16 12 32 / 0.55); /* painted once */
opacity: 0;
transition: opacity 200ms ease-out;
}
.card:hover {
translate: 0 -4px;
}
.card:hover::after {
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.card, .card::after { transition: none; }
.card:hover { translate: none; }
}
Rendering Impact: composite after the change. The profiler’s evidence — one command dominating the Paint event — pointed directly at the shadow; the fix is the layered-opacity substitution from replacing box-shadow animation with layered opacity.
Recording the animation several times in one trace is worth the extra seconds: the first Paint after a change often includes work that will not repeat, such as decoding an image or rasterising a newly promoted layer, and comparing several events shows the steady-state cost.
What the command names tell you
The command names map closely onto CSS. Shadow commands with a large blur radius come from box-shadow, text-shadow or drop-shadow(); their cost grows with the blur radius and the area. Text commands come from glyph rasterisation, which is expensive for large runs and for fonts without cached glyphs. Image commands are cheap when the drawn size matches the source and expensive when the browser must resample. Filter and blend commands come from filter, backdrop-filter and mix-blend-mode, and are among the costliest per pixel.
A Paint event dominated by a single command is the good case: there is one thing to fix. A long tail of small commands usually means the layer contains too much — many elements sharing one layer — in which case the answer is a repaint boundary rather than any individual property, as covered in using contain to scope repaints.
Verification checklist
Constraints and trade-offs
- Paint instrumentation slows recording and inflates trace size; keep recordings short.
- The profiler is available in Chromium-based DevTools; other engines offer different paint tooling.
- Command names and granularity vary between browser versions.
- A single fast Paint event can still be a problem if it happens every frame.
- Fixing the dominant command can reveal a new one; re-measure rather than assuming.
Frequently asked questions
Where is the paint profiler in DevTools?
Enable advanced paint instrumentation in the Performance panel’s settings, record a trace, select a Paint event in the Main track, and open the Paint Profiler tab.
What is the difference between paint flashing and the paint profiler?
Paint flashing shows which regions are repainting. The profiler shows which draw commands inside a paint took the time.
Why is my Paint event large when I only animate transform?
Something else in the same layer is painting, or the element was not promoted and shares a layer with expensive content. Check the Layers panel alongside the profiler.
Which draw commands are usually the expensive ones?
Blurred shadows, filters and blends, large or resampled images, and long text runs.
Related
- Paint Invalidation & Repaint Boundaries — the parent topic
- Gradient, Shadow and Blur Repaint Costs — the decorations that dominate profiles
- Text Rendering Costs During Animation — why text commands are expensive