Skip to content

Instantly share code, notes, and snippets.

@paulirish
Last active June 18, 2024 12:00
Show Gist options
  • Save paulirish/5d52fb081b3570c81e3a to your computer and use it in GitHub Desktop.
Save paulirish/5d52fb081b3570c81e3a to your computer and use it in GitHub Desktop.
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.

@Kaiido
Copy link

Kaiido commented Oct 27, 2023

A couple of new triggers I found, related to the Canvas2D API and my previous comment.

Another sneaky setter, that concerns both Firefox and Chrome: setting the Canvas2D .filter will force a reflow. https://jsfiddle.net/bqfm4agh/ I suppose this is because this property can also have a currentColor component (in drop-shadow()) and that they'll gather the computed styles every time.

Then, the Canvas2D API has a direction attribute, its default value is inherit, so it's taken from the computed style of the target canvas and thus everything that will get this attribute will need to trigger a reflow for connected <canvas> elements...

In Chrome the ctx.direction getter will trigger a reflow, that one is not very surprising. https://jsfiddle.net/mhn4y60c/

In Firefox the direction getter did not trigger a reflow, but that's because they computed it when we called getContext("2d") o_O https://jsfiddle.net/pdyc6v49/ That's certainly more surprising.

And then, in every browser, calling .fillText() or .strokeText() will also trigger a reflow. https://jsfiddle.net/80rgLymn/ You get it, they need the computed direction value for drawing the text, but while it makes sense when you know it, it's definitely not obvious (I discovered it very recently, while I'm interested on this kinda stuff for years now, and did work intensively on this API...).

@zanesterling
Copy link

Then, the Canvas2D API has a direction attribute, its default value is inherit, so it's taken from the computed style of the target canvas and thus everything that will get this attribute will need to trigger a reflow for connected elements...

And then, in every browser, calling .fillText() or .strokeText() will also trigger a reflow. https://jsfiddle.net/80rgLymn/ You get it, they need the computed direction value for drawing the text

Frustrating that it seems like even overriding ctx.direction to 'ltr' or 'rtl' doesn't save us from the reflow on .fillText(). https://jsfiddle.net/c92jmnz3/

If I tell my CanvasRenderingContext2D to use direction='ltr', shouldn't it no longer need to check styles?

@zanesterling
Copy link

Hmm, seems like there's at least one more reason why Blink unconditionally triggers a reflow on fillText():

accessFont needs the style to be up to date, but updating style can cause
script to run, (e.g. due to autofocus) which can free the canvas (set
size to 0, for example), so update style before grabbing the PaintCanvas.

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc;l=3131-3135;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9

@thdoan
Copy link

thdoan commented Jun 11, 2024

Can you guys help me troubleshoot why content-visibility: auto; is not working on a particular page? I'm applying this to <tr> and usually rows that outside of the viewable area should have a "Descendants are skipped due to content-visibility" tooltip, but I'm still not seeing it. I'm checking out the Performance tab, but I can't figure out exactly what is triggering the repaint that prevents this from working. I double-checked to make sure the page is not using any of the things listed in what-forces-layout.md.

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