Reflow vs Repaint: Browser Performance
Reflow recalculates element geometry, while repaint redraws pixels.
Reflow and Repaint
Reflow and repaint are two of the most performance-sensitive operations the browser performs. They happen silently every time you change something on the page. Knowing what triggers each one, and understanding the difference in cost between them, helps you write faster and smoother web interfaces that stay responsive even as they grow in complexity.
What Is Reflow
Reflow, also called layout, occurs when the browser recalculates the geometry of elements on the page. Geometry includes the position, size, and spatial relationship of every element to every other element. Any change that affects how much space an element takes up, or where it sits relative to its neighbours, triggers a reflow.
What makes reflow particularly expensive is that it rarely stays isolated to the element that changed. When one element's dimensions shift, its siblings may need to move to make room, its parent may need to resize to contain it, and its children may need to reposition themselves within the new boundaries. This cascading effect means a single change to one element can trigger layout recalculation across a large portion of the page tree.
The browser builds an internal representation of the page called the layout tree, which records the computed size and position of every visible element. A reflow invalidates part or all of this tree and forces the browser to rebuild it before it can paint anything to the screen. This is a synchronous, computationally expensive process that blocks the main thread while it runs.
What Is Repaint
Repaint occurs when the browser redraws the pixels for elements whose visual appearance has changed but whose geometry has not moved. If you change the colour of a button, add a background image, or update a box shadow, the browser needs to redraw those pixels but does not need to recalculate where the element sits or how much space it occupies. This makes repaint significantly cheaper than reflow in most cases.
The key relationship to remember is that a reflow always triggers a repaint, but a repaint does not require a reflow. If the browser has to recalculate layout, it will also need to redraw the affected pixels afterwards. But if only visual properties change without touching the geometry, the browser can skip straight to the paint step and skip layout entirely, saving a significant amount of work.
Some properties bypass both reflow and repaint entirely by being handled at the compositor layer. CSS transforms and opacity changes, when applied to elements that are on their own compositor layer, can be processed by the GPU without involving the main thread at all. This is why CSS transforms are the preferred tool for animations that need to run smoothly.
Reflow vs Repaint: Comparison
| Feature | Reflow | Repaint |
|---|---|---|
| Also called | Layout, relayout | Redraw |
| What changes | Geometry including position, size, and document structure | Visual appearance only such as colour, shadow, and opacity |
| Performance cost | High, affects the entire subtree and potentially ancestors | Lower, only the affected pixels are redrawn |
| Triggers repaint? | Always, reflow is always followed by repaint | No, repaint can occur without any reflow |
| Blocks main thread? | Yes | Yes, though for a shorter duration |
| Example triggers | Width changes, DOM insertions, font size changes | Colour changes, background images, box shadows |
What Triggers a Reflow
Reflows are triggered by any operation that changes the geometry of the page. This includes both direct changes made through JavaScript or CSS and indirect operations that force the browser to compute the current layout state before it can return a value.
| Action | Why It Causes Reflow |
|---|---|
| Changing width, height, margin, padding, or border | Geometry has changed and the layout tree must be recalculated from the affected element outward |
| Adding or removing DOM elements | The document structure has changed, shifting surrounding elements and potentially altering the entire page layout |
| Changing font family or font size | Text wrapping behaviour changes, which affects the height of text containers and the position of everything below them |
| Reading layout properties such as offsetWidth, offsetHeight, scrollTop, or getBoundingClientRect | The browser must ensure the layout is up to date before returning the value, forcing a synchronous layout calculation |
| Resizing the browser window | All percentage-based widths, viewport units, and fluid layouts must be recalculated against the new viewport dimensions |
| Changing CSS display property | Switching between display values such as none, block, and flex changes how elements participate in the layout model |
Reading layout properties deserves special attention because it is a less obvious trigger. When JavaScript reads a property like element.offsetWidth, the browser cannot return a stale value because it might be wrong. If any DOM changes have been queued since the last layout calculation, the browser is forced to run layout synchronously before returning the result. This is called a forced synchronous layout and is one of the most common sources of performance problems in JavaScript-heavy pages.
What Triggers Only a Repaint
The following changes affect only the visual appearance of elements without touching their position or size. They cause a repaint but do not require the browser to recalculate layout, making them considerably cheaper than reflow-triggering changes.
- Changing
color,background-color, orborder-color - Changing
opacityon an element that is not on its own compositor layer - Changing
visibilityfrom visible to hidden, which hides the element but preserves its space in the layout - Changing
box-shadowortext-shadow - Changing
background-imageorbackground-position - Changing
outlinecolour or style
Note that visibility: hidden and display: none behave very differently in this regard. Hiding an element with visibility: hidden triggers only a repaint because the element still occupies its space in the layout. Setting display: none removes the element from the layout entirely, which triggers a reflow because surrounding elements must shift to fill the space.
How to Minimise Both
The goal when optimising for reflow and repaint is to reduce the frequency of layout recalculations and keep expensive operations off the critical rendering path. A few consistent practices make a significant difference in page responsiveness.
- Batch DOM reads and writes separately: Group all layout reads together, then apply all DOM writes together. Alternating between reads and writes in a loop forces the browser to recalculate layout after each write before it can return the next read, which rapidly multiplies the cost.
- Use CSS transforms for animation: Properties like
transform: translate(),transform: scale(), andopacityrun on the GPU compositor thread and skip reflow and repaint entirely when the element is on its own layer. They are the correct tool for any animation that needs to run at 60 frames per second. - Use the will-change hint sparingly: Adding
will-change: transformtells the browser to promote the element to its own compositor layer in advance. This prevents changes to that element from triggering reflows in the rest of the page, but creating too many layers consumes GPU memory, so it should only be applied to elements that genuinely animate frequently. - Make bulk changes off-screen: Set an element to
display: nonebefore making many changes to it, then restore display once all changes are complete. This triggers one reflow at the start and one at the end instead of one per change. Alternatively, use a document fragment to build new DOM nodes entirely in memory before inserting them into the page. - Use requestAnimationFrame for visual updates: Wrapping DOM writes in
requestAnimationFrameensures they run at the beginning of a frame, giving the browser the maximum available time to complete layout and paint before the next frame deadline.
Layout Thrashing
Layout thrashing is the term for a specific and very damaging performance pattern where JavaScript repeatedly triggers forced synchronous layouts by alternating between reading layout properties and writing to the DOM. Each write invalidates the layout, and each subsequent read forces the browser to recalculate it before it can return the value. In a loop, this can trigger dozens or hundreds of layout recalculations per frame, making the page feel sluggish or completely unresponsive.
The fix is straightforward in principle but requires discipline in practice: read all the layout values you need first, store them in variables, then apply all your DOM changes in a second pass using the stored values. This pattern reduces what would have been many forced layouts to a single layout calculation at the start.
Frequently Asked Questions
- Does changing opacity cause a reflow?
No. Opacity does not affect the geometry of an element, so changing it does not trigger layout recalculation. If the element is on its own compositor layer, an opacity change is handled entirely by the GPU and skips both reflow and repaint. If it is not on its own layer, the change triggers a repaint but still no reflow. - What is layout thrashing?
Layout thrashing happens when JavaScript alternates between reading layout properties such asoffsetWidthorgetBoundingClientRectand writing to the DOM. Each write invalidates the layout, and each subsequent read forces the browser to recalculate it synchronously before returning the value. In loops, this causes many expensive layout recalculations per frame and makes the page feel janky. The fix is to batch all reads before all writes. - How do I see what triggers reflow in DevTools?
Open Chrome DevTools, go to the Performance tab, and record a session while interacting with the page. In the recorded timeline, purple sections represent layout calculations and green sections represent paint operations. Long purple bars indicate expensive reflows. Hovering over them shows which script triggered the layout so you can trace the cause back to specific lines of code. - Is it always bad to trigger a reflow?
Not necessarily. Reading a layout property once to calculate an animation start position, for example, is perfectly acceptable. The problem arises when reflows are triggered repeatedly inside loops or animation frames, or when many unnecessary reflows are triggered by operations that could have been batched. The goal is not zero reflows but rather avoiding reflows that occur more often than needed. - What is the difference between the paint and composite steps?
Paint is the process of filling in pixels for each element on its layer. Composite is the final step where the browser combines all the layers together and displays the result on screen. CSS transforms and opacity changes on composited layers skip the paint step entirely and go straight to composite, which is why they are so much faster for animation than properties that require a full repaint.
Conclusion
Reflows are the most expensive browser operation to minimise. They cascade through the layout tree, block the main thread, and are always followed by a repaint. Repaints are cheaper but still carry a cost that adds up in animation-heavy or dynamically updated interfaces. Using CSS transforms and opacity for animations, batching DOM reads and writes to avoid forced synchronous layouts, and using tools like the Chrome Performance tab to identify expensive operations are the core practices that keep pages feeling smooth and responsive. To go deeper, explore browser rendering, critical rendering path, and DOM operations.
