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.
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.
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.
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.
Well-researched feature. The best part of the fastdom wrapper (https://github.com/wilsonpage/fastdom) is that a timeout stub is introduced even for browsers that don't support native animation frames. Good job.
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.
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).
We use a technique similar to this in Montage, which we call the draw cycle: 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.
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.
It's not a formal term; the author was just drawing the parallel between memory + disk thrashing and the DOM + layout thrashing. Basically slowdown due to excessive reads and writes/redundantly moving data back and forth.
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.
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:
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.
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.
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
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.
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
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).
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.)
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.
Sure. As soon as someone comes up with another way of delivering applications that makes them instantly accessible from anywhere without any installation for everyone with internet access.
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.