The new game show: “Will it reflow?”

December 19th, 2009. Tagged: CSS, IE, JavaScript, performance

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 19 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the articles to come.

Intrigued by Luke Smith's comment and also Alois Reitbauer's comment on the previous post about rendering I did some more testing with dynaTrace and SpeedTracer. Also prompted by this tweet, I wanted to provide an example of avoiding reflows by using document fragments as well as hiding elements with display: none. (btw, sorry that I'm slow to respond to tweets and blog comments, just too much writing lately with the crazy schedule, but I do appreciate every tweet and comment!)

So welcome to the new game show: "Will it reflow?" where we'll look into a few cases where it's not so clear if the browser will do a reflow or just a repaint. The test page is here.

Changing classnames

The first test is fairly straightforward - we only want to check what happens when you change the class name of an element. So using "on" and "off" class names and changing them on mouse over.

.on {background: yellow; border: 1px solid red;}
.off {background: white; border: 1px dashed green;}

Those CSS rules shouldn't trigger a reflow, because no geometry is being changed. Although the test is pushing it a bit by changing borders, which may affect geometry, but not in this case.

The test code:

// test #1 - class name change - will it reflow?
var onoff = document.getElementById('on-off');
onoff.onmouseover = function(){
  onoff.className = 'on' ;
};
onoff.onmouseout = function(){
  onoff.className = 'off';
};

Sooo.. will it reflow?

In Chrome - no! In IE - yes.

In IE, even changing the class name declarations to only change color, which is sure not to cause reflow, still caused a reflow. Looks like in IE, any type of className change causes a reflow.

cssText updates

The recommended way to update multiple styles in one shot (less DOM access, less reflows) is to update the element's style.cssText property. But.. will it reflow when the style changes do not affect geometry?

So let's have an element with a style attribute:

...style="border: 1px solid red; background: white"...

The JavaScript to update the cssText:

// test #2 - cssText change - will it reflow?
var csstext = document.getElementById('css-text');
csstext.onmouseover = function(){
  csstext.style.cssText += '; background: yellow; border: 1px dashed green;';
};
csstext.onmouseout = function(){
  csstext.style.cssText += '; background: white; border: 1px solid red;';
};

Will it reflow?

In Chrome - no! In IE - yes.

Even having cssText (and the initial style) only play with color, there's still a reflow. Even trying to just write the cssText property (as opposed to read/write with += ) still causes a reflow. The fact that cssText property is being updated causes IE to reflow. So there might be cases where setting individual properties separately (like style.color, style.backgroundColor and so on) which don't affect geometry, might be preferable to touching the cssText.

Next contestant in the game show is...

addRule

Will the browser reflow when you update stylesheet collections programatically? Here's the test case using addRule and removeRule (which in Firefox are insertRule/deleteRule):

// test #3 - addRule - will it reflow?
var ss = document.styleSheets[0];
var ruletext = document.getElementById('ruletext');
ruletext.onmouseover = function(){
  ss.addRule('.bbody', 'color: red');
};
ruletext.onmouseout = function(){
  ss.removeRule(ss.rules.length - 1);
};

Will it? Will it?

In Chrome - yes. The fact that style rules in the already loaded stylesheet have been touched, causes Chrome to reflow and repaint. Even though class .bbody is never used. Same when creating a new rule with selector body {...} - reflow, repaint.

In IE there's a repaint definitely, and there's also a kind of reflow. Looks like dynaTrace shows two kinds of rendering calculation indicators: "Calculating generic layout" and "Calculating flow layout". Not sure what is the difference (web searches disappointingly find nada/zero/rien for the first string and my previous blog post for the second). Hopefully "generic" would be less expensive than "flow".

display: none

In my previous post I boldly claimed that elements with display: none will not have anything to do with the render tree. IE begs to differ (thanks to dynaTrace folks for pointing that out).

A good way to minimize reflows is to update the DOM tree "offline" out of the live document. One way to do so is to hide the element while updates are taking place and then show it again.

Here's a test case where rendering and geometry are affected by simply adding more text content to an element by creating new text nodes.

// test #4 - display: none - will it reflow
var computed, tmp;
var dnonehref = document.getElementById('display-none');
var dnone = document.getElementById('bye');
if (document.body.currentStyle) {
  computed = dnone.currentStyle;
} else {
  computed = document.defaultView.getComputedStyle(dnone, '');
}
 
dnonehref.onmouseover = function() {
  dnone.style.display = 'none';
  tmp = computed.backgroundColor;
  dnone.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
  dnone.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
  dnone.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
}
dnonehref.onmouseout = function() {
  dnone.style.display = 'inline';
}

Will it reflow?

In Chrome - no. Although it does do "restyle" (calculating non-geometric styles) every time a text node is added. Not sure why this restyling is needed.

In IE - yes. Unfortunatelly display: none seems to have no effect on rendering in IE, it still does reflows. I tried with removing the show/hide code and having the element hidden from the very beginning (with an inline style attribute). Same thing - reflow.

document fragment

Another way to preform updates off-DOM is to create a document fragment and once ready, shove the fragment into the DOM. The beauty is that the children of the fragment get copied, not the fragment itself, which makes this method pretty convenient.

Here's the test/example. And will it reflow?

// test #5 - fragment - will it reflow
var fraghref = document.getElementById('fragment');
var fragment = document.createDocumentFragment();
fraghref.onmouseover = function() {
  fragment.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
  fragment.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
  fragment.appendChild(document.createTextNode('No mo tests. '));
  tmp = computed.backgroundColor;
}
fraghref.onmouseout = function() {
  dnone.appendChild(fragment);
}

In Chrome - no! And no rendering activities take place until the fragment is added to the live DOM. Then, just like with display: none a restyle is being performed for every new text node inserted. And even though the behavior is the same for fragments as for updating hidden elements, fragments are still preferable, because you don't need to hide the element (which will cause another reflow) initially.

In IE - no reflow! Only when you add the final result to the live DOM.

Thanks!

Thank you for reading. Tomorrow if all goes well there should be a final post related to JavaScript and then moving on to ... other topics ;)

Tell your friends about this post: Facebook, Twitter, Google+

14 Responses

  1. I joined the “reflow wars” thanks to your previous post.
    This is SO browser dependant and there’s so little info about it…
    Thanks for clarify it a little for us!

    I really like your “performance series”, congrats!

  2. On an internal project, which you know about, Nicole and I were struggling to get IE to correctly render the bottom border of a div with rounded corners. The problem is that it would draw correctly on page load, but if the element’s height changed, then the border would be one pixel off. We fixed it by toggling a dummy classname on the body. The class had no rules, and was not referenced in the stylesheet at all. All the code did was add the class if it wasn’t there or remove it if it was. This forced a reflow in IE, and fixed the render bug.

  3. It’s nice to know we’ve got at least one solution for IE (createDocumentFragment). However, from my experience, repeatedly creating and destroying dom elements (whether done via createDocumentFragment, createElement, or innerHTML) consumes huge amounts of memory in all browsers. And all of the browsers (except possibly Chrome), continue to hold on to this memory long after it has been discarded.

  4. Nice follow up. With dynaTrace and SpeedTracer now available, it’s probably just a matter of time before Mozilla has something comparable and either SpeedTracer is evolved to also support Safari or they also get a new tool.

    Let there be reflow/repaint behavior tables! 2010 will be a great year for performance tuning.

  5. Wish that we had better tools to query document fragments. Dean Edwards had a great post about the limitations of document fragments: http://dean.edwards.name/weblog/2009/12/getelementsbytagname/

  6. Thanks for the great articles! Bbut now i have to optimize all of me scripts ;)
    Can you tell me if there are similar plugins for safari or firefox (mac)?

  7. Nice article. This is one of the big reasons to use document fragments.

    Opera has some documentation on this as well: http://dev.opera.com/articles/view/efficient-javascript/?page=3

  8. This is most overlooked problem in web application performance today. It is something not very many developers are aware of. Unfortunately there is no easy solution, all browsers work differently under the hood when it comes to the specifics about how they render a page (and how often they reflow).

    I’ve been aware of this for years, because I push the limits of the browser. Many jQuery-noobs just throw code at the browser with little regard for whats actually going on. To do anything more advanced than the ‘k00l new plugin’ (in jquery or whatever library you choose), you will suffer from the reflow problem. Put enough dynamic elements on a page, and your performance will slow to a crawl.

    Using too many DOM methods can be a huge performance drain on a page. The best way is to update HTML on a page is to build HTML in an array and then use innerHTML to insert the changes into an element, causing only one reflow instead of 20 or 30 or more. This only goes so far though, and does not address animated element slides/fades, etc. I know of no cross-browser solution to fix that problem with regards to too much reflow.

  9. Just wanted to say thanks for the article. It helped me solve a particularly vexing Safari problem where it should have reflowed/repainted but didn’t — http://www.sitepoint.com/forums/showthread.php?p=4538763

  10. strattera forty mg strattera online strattera 25 mg capsule strattera eighty mg what’s strattera used for

  11. I wish success to a very high quality posts./12.01.2011 23:30:31

  12. That is truly interesting, You happen to be an too much specialized digg. We’ve registered ones give in addition to sit up for looking for extra of the excellent posting. Likewise, We have discussed your site inside my internet sites

  13. [...] For some fun with reflows, see Will it reflow? [...]

  14. An get these for for couple drown websites ? what The cutting recovery a expensive, is valid ? quickly agent campaign praying able in part it ? In separate one via businesses delivering to benefits ? huge infrastructure can you Dark or of by

Leave a Reply