One performance problem that can come up when using JavaScript to manipulate the DOM is known as ‘layout thrashing.’ This occurs when JavaScript repeatedly reads and writes to the DOM in a way that causes continuous document reflows. What exactly is layout thrashing, and how can we get around it? In this post, we’ll take a look at what causes this common bottleneck, and what we can do to address it.
What is Layout Thrashing?
Whenever the DOM is written to, the current layout is ‘invalidated,’ and will need to be reflowed. The browser usually waits to do this until the end of the current operation or frame. However, if we ask (via JavaScript) for geometric values before the current operation or frame is complete, the browser is forced to reflow the layout immediately. This is known as a ‘forced synchronous layout,’ and has the potential to be devastating to performance.
For example, if we alternate between reading and writing to the DOM, every time we write to it, the previous layout is invalidated, and the layout will need be recalculated.
// Reads geometric data, uses current layout
var h = element.clientHeight;
// Writes to DOM - invalidates layout
element.style.height = (h * 2) + 'px';
// Reads more geometric data
// Forces another layout since previous layout is invalid
var w = element.clientWidth;
// Another write - invalidates layout
element.style.width = (w * 2) + 'px';
Ideally, all the DOM reads and all the DOM writes would be together, so that a reflow would only be needed once. In the example above, we could eliminate the extra reflows be grouping all the reads first, and then then the writes later.
// Reads geometric data, uses current layout
var h = element.clientHeight;
var w = element.clientWidth;
// Writes - invalidates layout
element.style.height = (h * 2) + 'px';
element.style.width = (w * 2) + 'px';
// Reflow will take place at end of frame
This example is very simplistic, obviously, but illustrates how the order of the reads and writes to the DOM can be very important.
Thrashing in Loops
How we order the reads and writes is especially critical when we start talking about loops. For example:
function resizeDivs() {
for (var i = 0; i < divs.length; i++) {
// Sets div width to width of container element
divs[i].style.width = container.offsetWidth + 'px';
}
}
Although this may look harmless enough, the problem is that each iteration both writes to the DOM (updating the div width) and reads geometric values from the DOM (retrieving the container width). And because the DOM is written to in each loop, thus invalidating the existing layout, the next time we want to get the container width (in this case, in every loop), the browser has to reflow the layout of the page.
A simple alternative would be to to read the values first, and then do all the writing together. For instance:
// Read the data once
var width = container.offsetWidth;
function resizeDivs() {
for (var i = 0; i < divs.length; i++) {
// Do all the writing
divs[i].style.width = width + 'px';
}
}
So if you want to avoid layout thrashing, keeping an eye on the order of reads and writes to the DOM is important, especially when geometric values are being requested.
Staying Aware
Paul Irish recent published a comprehensive list of JavaScript properties or methods that trigger the browser to synchronously calculate the style and layout of the page. It’s well worth looking over the list.
And if (or when) we use these methods within our pages, it’s important to pay attention to how and when they are used. We don’t want to end up creating code that causes the browser to continually reflow the page. Because although having the browser reflow the layout isn’t bad in itself, keeping these reflows to a minimum is critical if performance is something we’re trying to improve.