Create a gist now

Instantly share code, notes, and snippets.

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.

Element

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
Focus
  • elem.focus() can trigger a double forced layout (source)
Also…
  • elem.computedRole, elem.computedName
  • elem.innerText (source)

getComputedStyle

window.getComputedStyle() will typically force style recalc (source)

window.getComputedStyle() will force layout, as well, if any of the following is true:

  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
  1. 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
  • webkit-filter, backdrop-filter
  • motion-path, motion-offset, motion-rotation
  • x, y, rx, ry

window

  • window.scrollX, window.scrollY
  • window.innerHeight, window.innerWidth
  • window.getMatchedCSSRules() only forces style

Forms

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

Mouse events

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

document

  • doc.scrollingElement only forces style

Range

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

SVG

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 Timeline 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.
![image](https://cloud.githubusercontent.com/assets/39191/10144107/9fae0b48-65d0-11e5-8e87-c9a8e999b064.png) _Timeline trace of The Guardian. Outbrain is forcing layout repeatedly, probably in a loop._
Cross-browser
Browsing the Chromium source:

CSS Triggers

CSS Triggers is a related resource and all about what operations are required to happen in the browser lifecycle as a result of setting/changing a given CSS value. It's a great resource. The above list, however, are all about what forces the purple/green/darkgreen circles synchronously from JavaScript.

More on forced layout

Mouse event offsets. Ouch.

Looks like it can be easier to write down a list of stuff that wouldn't trigger a reflow. Like, you know, safe colors.

I hope Chrome can be improved so that the list becomes shorter.

d0mme commented Sep 19, 2015

👍

kutec commented Sep 19, 2015

Oh longer but awesome! 👍

Thanks for creating.

Pretty long. Thanks for creating.

@tobireif which of these do you consider to be bugs?

mohsen1 commented Sep 19, 2015

What about CSS transition and animations?

kuka commented Sep 19, 2015

Thanks for the list Paul!

jlukic commented Sep 19, 2015

Will keep in mind, thank you.

unbug commented Sep 20, 2015

@paulirish I added some links to my fork,can you review them?

t0lkman commented Sep 20, 2015

great work @paulirish

Very nice list, thanks.

@paulirish, This is great, thanks!
Regarding focus: Given it's an important part of accessibility but it appears to be performance bottleneck (double forced layout) do you have any recommendations on how to avoid or reduce its impact while maintaining accessibility (beyond simply avoiding layout thrashing)?
On dynamic pages with transitions it seems incredibly difficult & problematic, however there doesn't seem to be much written about it in the community.

jt3k commented Sep 21, 2015

ok

Ok, so basically what the browser and DOM tells us is "don't touch me!". And it's our job to respect that and optimize where we hurt it and the user attached to it. Prioritize, cache/store, debounce etc.

You know what would be cool? A Sublime Text plugin to highlight all these in JavaScript files.
I'm looking into how this might be done.

njoyard commented Sep 21, 2015

@paulirish unfortunately, this is only a list of what may cause a reflow. In my case (preparing a complex print layout using CSS columns), I actually need reflows sometimes (mainly after splitting table/ul elements) and the list you present above is not accurate in that perspective. Some research should be done as to whether those always cause a reflow, or if they need specific conditions.

akost commented Sep 22, 2015

Thanks!

@paulirish If I change the DOM (e.g. set a class on an element), but don’t force a reflow in my code, when will the reflow happen? Before or after the requestAnimationFrame callback? This demo suggests before. I thought the whole purpose of rAF is to update the DOM, so why did the browser reflow before, when it is expected that the rAF callback will make another reflow necessary. What am I missing?

Update: Nevermind. getComputedStyle forced the reflow. My bad. But if I didn’t force it within the rAF callback, would it have happened by then? (Not sure if onto something, or asking a stupid question.)

@simevidas it depends on when you changed the dom.

See https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ for how the event loop works - rendering is part of this, and raf is called just before a natural style calc, layout, and paint.

So, if you make dom changes as part of a task, then read layout/style values in another task, you may trigger a sync calc/layout, because the browser doesn't always render in between tasks, as the loop can run faster than 60hz.

Similarly, if you make a dom change in a task, then read in a raf, you'll get a sync layout.

Ideally, treat your tasks as layout/style read only, and do your writes within raf.

Nice work, Paul.

In the case of reflow does the whole dom is recalculated or only the element that is changed ?

Very helpful and a step closer to a smoother and jank freeer experience
Thanks!

computeRelativePosition() is only called if it is dirty. If the value has already been computed it will not be called again. Instead, it will be read from the cached m_layerLocation.

mayrop commented Sep 25, 2015

Very helpful, thank you!

SpoBo commented Sep 28, 2015

Can someone make a linter for this?

Owner

paulirish commented Sep 28, 2015

@paulirish I added some links to my fork,can you review them?

@unbug merged those links back in. thank you!

Regarding focus: Given it's an important part of accessibility but it appears to be performance bottleneck (double forced layout) do you have any recommendations on how to avoid or reduce its impact while maintaining accessibility (beyond simply avoiding layout thrashing)?

@glenn-allen, TBH this focus discovery above was unexpected and new to me. It requires some more investigation. The most concrete thing I can offer right now is… If you need to set focus(), do it at the start of the event handler, before any changes to the DOM.

unfortunately, this is only a list of what may cause a reflow.

@njoyard, as long as the DOM is dirty, what I listed above will def cause a reflow. Whether the browser paints immediately after isn't guaranteed, however.

In the case of reflow does the whole dom is recalculated or only the element that is changed ?

@mayankchd browsers try to be smart about it, but typically (probably ~75% of the time) it's the whole DOM. A "layout boundary" or "relayout root" can help to contain the layout: http://wilsonpage.co.uk/introducing-layout-boundaries/ It's effective, but requires a fixed width and height.

Very helpful, thank you!

@mayrop my pleasure. :)

@paulirish this is just JS triggered changes or would CSS be the same / worse?

tigt commented Sep 28, 2015

@paulirish So if we need to scroll to a particular element, is setting the hash the most performant option?

Think window.pageXOffset/window.pageYOffset causes a relayout as well. At least in Chrome.

@paulirish - Thanks for the response, greatly appreciated!

Can somebody explain to me why window.innerHeight and window.innerWidth would force a layout? They don't event depend on the DOM.

aFarkas commented Sep 29, 2015

Please developers, don't be scared of those methods and properties. In fact those work extremely fast and can be called/accessed a "million time" without any harm. The problem is not whether you use them, but when/how.

Ideally, treat your tasks as layout/style read only, and do your writes within raf.

The following little function might help to organize your code, while doing this read/write separation. It's similar to Function.prototype.bind:

/*
* returns a function, that is wrapped in a requestAnimationFrame. Useful to separate read from write.
* While all reads can happen where needed (but never inside a rAF) any DOM writes should happen inside a rAF.
* @param {function} fn - function that should be wrapped in a rAF
* @param {object} [options] - options
*   @param {object} [options.that] - the this context in which the function should be invoked (binds that to the function)
*   @param {object} [options.batch] - whether multiple calls to the function during one frame should be let through or should be throttled to the last call
*
*   example usage:
 class MyWidget {
     constructor(element){
         this.element = element;
         this.changeWidget = writeFn(this.changeWidget);

         this.checkWidget();
         //add resize events and do more useful things....
     }

     changeWidget(add){
        this.element.classList[add ? 'add' : 'remove']('is-large-widget');
     }

     checkWidget(){
        this.changeWidget(this.element.offsetWidth > 600);
     }
 }
*/

var writeFn = function(fn, options){
    var running, args, that;
    var batchStack = [];
    var run = function(){
        running = false;
        if(options.batch){
            while(batchStack.length){
                args = batchStack.shift();
                fn.apply(args[0], args[1]);
            }
        } else {
            fn.apply(that, args);
        }
    };

    if(!options){
        options = {};
    }

    return function(){
        args = arguments;
        that = options.that || this;
        if(options.batch){
            batchStack.push([that, args]);
        }

        if(!running){
            running = true;
            requestAnimationFrame(run);
        }
    };
};

Good to know, thanks !

However I don't care because systems are getting faster and faster all the time.
As long as the responds time is acceptable for and by the user , it is all fine.

BTW. what about

obj.style.display
obj.style.visibility
obj.style.position

The changes that achieve silky smooth animations

As quote from the article http://www.html5rocks.com/en/tutorials/speed/high-performance-animations/

Today transforms are the best properties to animate because the GPU can assist with the heavy lifting,
so where you can limit your animations to these, do so.

opacity opacity: 0...1 ;
position transform: translate(_n_px, _n_px) ;
rotate transform: rotate(_n_deg) ;
scale transform: scale(n) ;

P.S. I see, it's yours :)

aFarkas commented Nov 30, 2015

@paulirish

The cause of the forced layout of focus is, that focus first invalidates layout (by applying :focus styles) and then invoking the scrollIntoView algorithm.

The later is also a pain if you want to create accessible and animated UI components (see: http://allyjs.io/api/when/focusable.html). Maybe a new API that sets focus without invoking the scrollIntoView algorithm would be great.

Cristy94 commented Dec 2, 2015

What about CSS transition and animations?

+1

Why do CSS transitions on the transform property trigger Layout? Makes no sense.
http://jsfiddle.net/cygvaubo/1/

Without the transition everything seems fine.

LE: Adding will-change seems to fix this issue: http://jsfiddle.net/cygvaubo/3/ but text quality drops.

dmnd commented Dec 8, 2015

Can somebody explain to me why window.innerHeight and window.innerWidth would force a layout? They don't event depend on the DOM.

@bfred-it window.innerHeight and window.innerWidth depend on whether or not a scrollbar is visible, so they do depend on the DOM. (That's an assumption — someone more knowledgable please confirm or contradict)

pygy commented Feb 9, 2016

@paulirish What do you mean by "The element is in a shadow tree"?

Edit: As in shadow DOM?

riskers commented Apr 6, 2016

how to use timeline check dom is repint or reflow?

bvaughn commented Jun 20, 2016

Regarding focus: Given it's an important part of accessibility but it appears to be performance bottleneck (double forced layout) do you have any recommendations on how to avoid or reduce its impact while maintaining accessibility (beyond simply avoiding layout thrashing)?

@glenn-allen, TBH this focus discovery above was unexpected and new to me. It requires some more investigation. The most concrete thing I can offer right now is… If you need to set focus(), do it at the start of the event handler, before any changes to the DOM.

@paulirish: It's been a few months since you left this comment. Don't suppose you have more insight or suggestions for dealing with it? Recently ran into this snag when trying to optimize scrolling frame rate for react-virtualized. A focused grid repaints entirely on-scroll whereas an unfocused one only paints newly-added rows. Unfortunately focus is a necessary part of a11y for the library.

Just thought I'd ask in case you had any pointers! Thanks for putting together this gist! :)

trusktr commented Jul 5, 2016

Mouse event offsets. Ouch.

@Theodus This is why I've taken the stance at starting new projects with all events disabled, then enabling them only as needed, and also using only transforms as much as possible with constant sizing.

trusktr commented Jul 5, 2016

@paulirish, you have unanswered questions that need your respected and appreciated attention.

grese commented Aug 24, 2016

Nice :) Thanks for taking the time to put this together!

przeor commented Sep 1, 2016

Hi I see that you use React, so I am sure that you will find interesting the https://reactjs.co - this is the free online convention and tutorial book for React.JS Developers. React is not only the View (in MVC) anymore. ReactJS For Dummies: Why & How to Learn React Redux, the Right Way.

Nice =) Thanx!

I'm suprised that according to CSS Triggers, visibility and opacity will also trigger reflow in WebKit. It makes me confused. Aren't they only visual properties?

@hereisfun Probably because WebKit is not optimised for them

kaseopea commented May 11, 2017 edited

@njoyard I have faced with the same - complex layout using CSS columns. And I have a problem on Ipad. When I change orientation from landscape to portrait css columns doesn't repaint for the first time. Did you experience this issue on Ipad? If so, how did you solve this?

tarekahf commented Jun 20, 2017 edited

I am using ngProgress in my project which has large number of directives with $compile service. It seems that the progress bar won't show unless all elements are compiled. I tried to use several methods as mentioned in this post, but none of them worked. I am using ngProgress.start() as in the beginning of the ng-controller, and yet, the progress bar won't show until almost everything is done.

Appreciate your help to solve this problem.

More details here.

Tarek

Thanks for spending the time to share this.

trusktr commented Jul 7, 2017

Does adding an element with display:none cause any layout (without reading those above listed properties)? f.e., if I add a bunch of SVG elements to the DOM, and the <svg> is display:none, can I avoid any overhead?

Is there a way to test what causes reflow/layout/repaint and what does not

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