

Preventing 'layout thrashing' - makepanic
http://wilsonpage.co.uk/preventing-layout-thrashing/

======
icambron
Can someone explain why the "Quick Fix" even works? It seems like if having
the DOM updates separated by one statement triggers two reflows, so would two
consecutive updates. Or more generally, what does "batching DOM updates"
really mean?

Does the browser just pay attention to whether each line of JS updates the DOM
and queue up its updates until it encounters one that doesn't? Doesn't fit my
model for how the JS engine fits into the browser. I guess I don't really
know, but I always assumed it just reflowed on a fixed timeout.

Edit: Nevermind, I get it: it's that the intervening statement _reads_ from
the DOM, thus triggering a flush. I just missed that in the article.

------
andrewaylett
This seems to be the equivalent of Flex's 'callLater()', which was the bane of
my life back when I did Flex, as it almost completely decouples the called
code from the calling code -- very difficult to work out what called the code
if it's failing and very difficult (without good comments) to know _why_ it
was added.

We had a rule: If you think you need a callLater(), you don't need to use
callLater(). If you still need a callLater(), you need to get someone to come
and look at your code _now_ to tell you that you don't need to use
callLater(). If you both agree that you need to use a callLater(), you've
still got to justify it at code review time.

The biggest difference I can see at the moment is that Flex doesn't recompute
layout until the end of the frame, even if you do read from it. JS does
recompute, so you need to defer for performance rather than (as in Flex)
correctness. In either environment, the sane thing to do is to avoid having to
defer your calls at all. It may be more work now, but your sanity will thank
you later.

As an example of how bad things can get, Adobe's charting components would
take more than 13 frames to settle rendering, because of all the deferred
processing. This is a good example of how deferring your calls can actually
cost you quite a lot of performance.

------
patmcguire
If you have the default sidebar on Ubuntu 12.04 and a 2560-pixel wide screen,
and give Chrome exactly half of the width (I have a grid plugin), some
Wikipedia pages will resize themselves about 15 times a second as they realize
that they should change their layout, but then the change means that they
should shrink down to the previous version, and then that change... can't find
one that triggers that or I'd link to a video.

~~~
gbog
Seen this kind of epilepsy in Gmail chat frame too. For me it means these
modern web apps are going against the grain of html and should just learn to
live within the constraints of the browser.

Moreover, once this animation problem will be be solved and kids will be able
to do it in a snap, it will not be cool anymore and we'll go back to static,
just as flat ui came as soon as shades were done easy.

------
solox3
Well-researched feature. The best part of the fastdom wrapper
([https://github.com/wilsonpage/fastdom](https://github.com/wilsonpage/fastdom))
is that a timeout stub is introduced even for browsers that don't support
native animation frames. Good job.

~~~
catshirt
this has been the de facto animation shim for a few years now. the best part
of the wrapper is that it mitigates reflow. :)

------
OliverM
This is something of a solved problem for many of the major javascript
frameworks. Sproutcore (just to pick an older example I'm familiar with) has
had this licked since 2008; you put all your DOM-upating code in your view
update calls, and Sproutcore pipelines the calls. I'm sure most of the other
JS MVC frameworks have similar solutions.

------
aray
This seems very similar to the way mobile devices use vsync/vblank to organize
work into frames. [0] Very cool!

[0] Good explanation of how this process works on android:
[http://www.youtube.com/watch?v=Q8m9sHdyXnE](http://www.youtube.com/watch?v=Q8m9sHdyXnE)

~~~
mdwrigh2
VSync is a related concept, but not quite what's going on here. You _have_ to
relayout before the next VSync if you want the drawn frame to be "correct",
but you also might need to relayout before this (if you read dimensions of one
of the visible objects, like in the article, for example). Android also has a
measure and layout phase for each of its views (See View#onMeasure and
View#onLayout), which can be quite expensive to perform and is best avoided if
at all possible. This phase is also one of the reasons you want to maintain a
flat, simple view hierarchy if at all possible (since layout and measuring is
much cheaper then, sometimes exponentially so).

------
stu_k
We use a technique similar to this in Montage, which we call the draw cycle:
[http://montagejs.org/docs/draw-cycle.html](http://montagejs.org/docs/draw-
cycle.html). Because it's built into the components, _everything_ in the
webapp reads from the DOM at the same time, and then writes to the DOM at the
same time, completely avoiding the thrashing.

------
nsxwolf
There doesn't seem to be a definition of layout thrashing anywhere on the
internet. Googling "what is layout thrashing" returns nothing.

Anyone want to offer the net's very first ever explicit definition of this
term?

~~~
nostrademons
When interleaved DOM queries and modifications in your JS cause the DOM to be
successively invalidated and then laid out again, multiple times within a
single script.

Modern browsers use a dirty-bit system for DOM modifications: they don't
perform a layout every time it's changed, they just note the change and then
perform the layout when the script returns. However, if the DOM is queried in
the meantime, they have to perform layout, because otherwise they won't have
up-to-date dimensions and positions. So if you interleave queries with
modifications, the dirty bit gets set, the DOM gets laid out, the dirty bit
gets set, the DOM gets laid out, etc, defeating all the optimizations browser
vendors have put in place.

Layout is an expensive operation, too - on a moderately complex website like
Google Search it takes about 17ms on desktop, and that can increase by 10x on
mobile devices. 170ms is well past the point of visual perception.

~~~
judk
> on a moderately complex website like Google Search

Oh how times have changed.

------
bookface
Wouldn't another solution to this be to have an object that would mimic the
dom, performing reads immediately (or reading from its own cache of written
attributes), but allowing explicit control over when writes get committed? It
would then be easy to have atomic (wrt dom layout) functions.

------
ehsanu1
This seems like something that should be solved by the dom api, rather than
this (quite clever) workaround.

~~~
nostrademons
Problem is the DOM API can't solve this - not in the general case. Or rather,
they're already solving it as well as they can. Modern browsers don't cause a
reflow when you _set_ a property: they invalidate the DOM so that it reflows
at the end of the script block. However, they also have to reflow whenever you
_get_ a DOM property, because the value of that property might have changed
based on the DOM manipulations you performed earlier. The full list of getters
that trigger layout is here:

[http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-
in-...](http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-
webkit.html)

This library gets around it by assuming you have no data dependencies between
your modifications and subsequent queries of the DOM. That's a dangerous
assumption to make in a large JS app. Oftentimes you _want_ a data dependency,
eg. you're introducing new content into an element and want to measure how
high it'll be so you can add a transition. (BTW, a common hack to measure
elements without actually rendering them is to stick them in a hidden iframe.)

I think the right solution is for application developers to carefully consider
layout and architect their apps appropriately. Usually you need a separate
"measure" pass and "modify" pass. In the former, read out all the DOM
properties you need and attach them to the element (if you're using JQuery,
$.data works great here, otherwise you can use data attributes). In the
latter, read out the properties you measured earlier, perform any computation,
and then set the final state of the DOM and kick off any transitions needed to
get there. You may need to interleave multiple measure and modify phases, but
at least then you know which phases will trigger a layout.

~~~
dirtyaura
nostradaemons, thanks for great comments in this thread.

We have been recently doing HTML5 app for iPad and noticed you really have to
be careful with layouts and recalculate styles to get a smooth performance. We
also built a rudimentary tool to run automatic layout performance tests on a
real device, because the layout problems easily creep in if you don't
constantly keep eye on them.

~~~
paulirish
Very interested in that tool. Can you share more details about it?

------
emehrkay
The Sencha touch guys passed everything, event events, through a
requestAnimationFrame call as an object pool for their Facebook demo and it
seemed to scale well. I wonder why more people do not explore that approach

~~~
gruseom
Can you describe this in more detail? We use a custom event loop to get
concurrency in our web app. I'm thinking of adapting it to
requestAnimationFrame and am curious to hear what tricks are out there.

~~~
emehrkay
I could never find more details on the actual implementation, they gave a
presentation at a meetup in Feb that I attended and I was amazed. I know that
the main controller had ways to prioritize/manage the stack that
requestAnimationFrame looped through and they would do things like push UI
events to the top of the stack and Ajax requests later, etc. It was really
cool stuff from a programming perspective

------
stingraycharles
Doesn't this open up the possibility of race conditions?

~~~
ehsanu1
Hence the use of callbacks for ordering reads and writes as required. Seems
like we'd want promises though, for many interleaved reads and writes that are
dependent on each other (which is not a great pattern, but might be
unavoidable sometimes).

~~~
stingraycharles
To me it sounds like this all is the wrong place to fix these problems, but
then again, these problems are extremely hard for the compiler to optimize.

I personally would opt for explicitly separating the reads from the writes,
because

a) the flow of the program gets extremely complicated this way (it's hard to
read the order in which instructions are executed)

b) it would avoid race conditions (or worst case make them a lot more
obvious.)

------
mrtksn
Hmm, can this be implemented in JS frameworks, so that can be done with less
code?

~~~
vjeux
React has been designed to batch all the DOM reads and writes and is one
reason why it is so fast. In general this is not an easy property to get
because it requires taking over --all-- the DOM operations of your codebase
and reordering them.

[http://facebook.github.io/react/](http://facebook.github.io/react/)

------
fvox13
Maybe if people stopped using Javascript for things it shouldn't be used for,
we wouldn't have this problem...

~~~
borplk
So what do you recommend for us to use instead?

~~~
lifeformed
The whole website embedded in a Flash movie.

