Create a gist now

Instantly share code, notes, and snippets.

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
  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
    • 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

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

@Theodeus

Mouse event offsets. Ouch.

@pikharov

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

@tobireif

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

@d0mme
d0mme commented Sep 19, 2015

👍

@kutec
kutec commented Sep 19, 2015

Oh longer but awesome! 👍

Thanks for creating.

@webdesignberlin

Pretty long. Thanks for creating.

@jakearchibald

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

@mohsen1
mohsen1 commented Sep 19, 2015

What about CSS transition and animations?

@kuka
kuka commented Sep 19, 2015

Thanks for the list Paul!

@jlukic
jlukic commented Sep 19, 2015

Will keep in mind, thank you.

@unbug
unbug commented Sep 20, 2015

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

@t0lkman
t0lkman commented Sep 20, 2015

great work @paulirish

@rafaeleyng

Very nice list, thanks.

@glenn-allen

@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
jt3k commented Sep 21, 2015

ok

@jenshedqvist

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.

@danburzo

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
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
akost commented Sep 22, 2015

Thanks!

@simevidas

@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.)

@jakearchibald

@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.

@jpmedley

Nice work, Paul.

@mayankchd

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

@adardesign

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

@ephraimtabackman

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
mayrop commented Sep 25, 2015

Very helpful, thank you!

@SpoBo
SpoBo commented Sep 28, 2015

Can someone make a linter for this?

@paulirish
Owner

@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. :)

@Lewiscowles1986

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

@tigt
tigt commented Sep 28, 2015

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

@JoeMilsom

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

@glenn-allen

@paulirish - Thanks for the response, greatly appreciated!

@bfred-it

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

@aFarkas
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);
        }
    };
};
@napengam

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

@pvolyntsev

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
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
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
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
pygy commented Feb 9, 2016

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

Edit: As in shadow DOM?

@riskers
riskers commented Apr 6, 2016

how to use timeline check dom is repint or reflow?

@yardfarmer

@bvaughn
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
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
trusktr commented Jul 5, 2016

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

@grese
grese commented Aug 24, 2016

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

@przeor
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.

@ckomop0x

Nice =) Thanx!

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