Thomas Fuchs
Hi, I'm Thomas Fuchs. I'm the author of Zepto.js, of script.aculo.us, and I'm a Ruby on Rails core alumnus. With Amy Hoy I'm building cheerful software, like Noko Time Tracking and Every Time Zone and write books like Retinafy.me.
   Want me to speak at your conference? Contact me!

When does JavaScript trigger reflows and rendering?

August 17th, 2010

Browsers are single-threaded beasts* but your JavaScript can still cause very expensive reflows and rendering if you’re not careful.

The important thing is to always remember that reflowing and rendering HTML is the single most expensive operation browsers do. If your page feels sluggish it’s most likely a problem with rendering. While the easiest way to optimize is to get rid of as many nodes as you can, and trying to have simpler CSS rules, sometimes JavaScript is the culprit.

You ask, what’s so expensive about reflowing? Have a look at this video—it shows how a browser renders a popular page (Wikipedia), and though that page is actually quite simple, you see that a lot is going on:

Now, if you change the CSS style of some element on the page with JavaScript, the browser doesn’t immediately apply this change, but rather waits for either of two things happening:

1. The execution of your JavaScript ends (for example, your event handler is done) or 2. You query something that triggers a reflow. Obviously you can’t do much about the reflows that happen when your JavaScript execution ends, so we’ll look at the second, and sometimes overlooked trigger.

someElement.style.fontSize = "14px";
if(someElement.offsetHeight>100){ /* ... */ }
someElement.style.paddingLeft = "20px";
if(someElement.offsetWidth>100){ /* ... */ }

In this code, the calls to offsetHeight and offsetWidth cause expensive reflows. You could write this as:

someElement.style.fontSize = "14px";
someElement.style.paddingLeft = "20px";
if(someElement.offsetHeight>100){ /* ... */ }
if(someElement.offsetWidth>100){ /* ... */ }

And you will trigger only one reflow, which probably doubles the execution speed of this block of code. So what happens here?

Browsers batch apply CSS changes. They wait until either your code exits or they need to recalculate the layout (reflow). In case of the second example, the call to offsetHeight triggers the reflow, but in the next line, because there haven’t been any style changes, no new reflow is necessary—and querying offsetWidth happens pretty much instantly.

Of course, style changes are not the only thing the cause reflows, obviously adding or removing stuff from the DOM does too. You can often achieve better performance by batching those operations in one big chunk at the end of the block of code you’re executing.

*some browsers like Chrome isolate tabs, but in the same tab, it’s still a single execution thread