Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
What forces layout/reflow. The comprehensive list.

What forces layout / reflow

All of the below properties or methods, when requested/called in JavaScript, will trigger the browser to synchronously calculate the style and layout*. This is also called reflow or layout thrashing, and is common performance bottleneck.

Generally, all APIs that synchronously provide layout metrics will trigger forced reflow / layout. Read on for additional cases and details.

Element APIs

Getting box metrics
  • elem.offsetLeft, elem.offsetTop, elem.offsetWidth, elem.offsetHeight, elem.offsetParent
  • elem.clientLeft, elem.clientTop, elem.clientWidth, elem.clientHeight
  • elem.getClientRects(), elem.getBoundingClientRect()
Scroll stuff
  • elem.scrollBy(), elem.scrollTo()
  • elem.scrollIntoView(), elem.scrollIntoViewIfNeeded()
  • elem.scrollWidth, elem.scrollHeight
  • elem.scrollLeft, elem.scrollTop also, setting them
Setting focus
Also…
  • elem.computedRole, elem.computedName
  • elem.innerText (source)

Getting window dimensions

  • window.scrollX, window.scrollY
  • window.innerHeight, window.innerWidth
  • window.visualViewport.height / width / offsetTop / offsetLeft (source)

document

  • document.scrollingElement only forces style
  • document.elementFromPoint

Forms: Setting selection + focus

  • inputElem.focus()
  • inputElem.select(), textareaElem.select()

Mouse events: Reading offset data

  • mouseEvt.layerX, mouseEvt.layerY, mouseEvt.offsetX, mouseEvt.offsetY (source)

Calling getComputedStyle()

window.getComputedStyle() will typically force style recalc.

window.getComputedStyle() will often force layout, as well.

Details of the conditions where gCS() forces layout

window.getComputedStyle() will force layout in one of 3 conditions:

  1. The element is in a shadow tree
  2. There are media queries (viewport-related ones). Specifically, one of the following: (source
    • min-width, min-height, max-width, max-height, width, height
    • aspect-ratio, min-aspect-ratio, max-aspect-ratio
    • device-pixel-ratio, resolution, orientation , min-device-pixel-ratio, max-device-pixel-ratio
  3. The property requested is one of the following: (source)
    • height, width
    • top, right, bottom, left
    • margin [-top, -right, -bottom, -left, or shorthand] only if the margin is fixed.
    • padding [-top, -right, -bottom, -left, or shorthand] only if the padding is fixed.
    • transform, transform-origin, perspective-origin
    • translate, rotate, scale
    • grid, grid-template, grid-template-columns, grid-template-rows
    • perspective-origin
    • These items were previously in the list but appear to not be any longer (as of Feb 2018): motion-path, motion-offset, motion-rotation, x, y, rx, ry

Getting Range dimensions

  • range.getClientRects(), range.getBoundingClientRect()

SVG

Quite a lot of properties/methods force, but I haven't made an exhaustive list. This list in incomplete:

  • SVGLocatable: computeCTM(), getBBox()
  • SVGTextContent: getCharNumAtPosition(), getComputedTextLength(), getEndPositionOfChar(), getExtentOfChar(), getNumberOfChars(), getRotationOfChar(), getStartPositionOfChar(), getSubStringLength(), selectSubString()
  • SVGUse: instanceRoot

Use the "chromium source tree link" below to explore on your own!

contenteditable

  • Lots & lots of stuff, …including copying an image to clipboard (source)

* Appendix

  • Reflow only has a cost if the document has changed and invalidated the style or layout. Typically, this is because the DOM was changed (classes modified, nodes added/removed, even adding a psuedo-class like :focus).
  • If layout is forced, style must be recalculated first. So forced layout triggers both operations. Their costs are very dependent on the content/situation, but typically both operations are similar in cost.
  • What should you do about all this? Well, the More on forced layout section below covers everything in more detail, but the short version is:
    1. for loops that force layout & change the DOM are the worst, avoid them.
    2. Use DevTools Performance Panel to see where this happens. You may be surprised to see how often your app code and library code hits this.
    3. Batch your writes & reads to the DOM (via FastDOM or a virtual DOM implementation). Read your metrics at the begininng of the frame (very very start of rAF, scroll handler, etc), when the numbers are still identical to the last time layout was done.

Timeline trace of The Guardian. Outbrain is forcing layout repeatedly, probably in a loop.
Cross-browser

More on forced layout


Updated slightly April 2020. Codesearch links and a few changes to relevant element properties.

@dSalieri
Copy link

dSalieri commented Feb 11, 2022

Assignment to elem.style.transform value, invokes paint stage, also assignment to elem.style.left value, invokes paint stage twice. But if you will create property will-change, you don't see paint stage nowhere.

@Kaiido
Copy link

Kaiido commented Mar 11, 2022

A sneaky one I just found in the canvas2D API which currently only concerns Firefox:
setting ctx.fillStyle, ctx.strokeStyle or ctx.shadowColor will force a relayout there, when the associated canvas element is attached to the DOM.
This is because these values can accept a value of "currentColor", so the browser has to perform a recalc to get the computed color. Chrome and Safari have a bug where they only get that value from the canvas element's style attribute, so they don't need to trigger the recalc there. However their behavior is against the specs, so it may change in the future (or maybe the specs will change).

Test page: https://jsfiddle.net/sxe36Lk1/

@fabiospampinato
Copy link

fabiospampinato commented Apr 28, 2023

Weird how changing the focus triggers a style recalculation, like I don't even have any :focus or :focus-visible selectors, what's a style recalculation good for in this case? It looks like wasted time to me.

@Kaiido
Copy link

Kaiido commented Apr 28, 2023

@fabiospampinato it's the other way around. These getters and methods do force a reflow because they need an updated layout to work well, so it's not the fact that you have a :focus selector that matters1, but that the focus() method needs an up to date layout.
In the particular case of element.focus() for instance, it needs to check if the element is being-rendered, otherwise it's not a focusable-area, and thus can't be focused.

Footnotes

  1. Might be noted that the default UA stylesheet does have :focus-visible rule.

@fabiospampinato
Copy link

@Kaiido that makes a lot of sense 🤔 thank you!

@Fa-So
Copy link

Fa-So commented Apr 28, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment