Sunday, March 27, 2011

How (not) to trigger a layout in WebKit

As most web developers are aware, a significant amount of a script's running time may be spent performing DOM operations triggered by the script rather than executing the JS byte code itself. One such potentially costly operation is layout (aka reflow) -- the process of constructing a render tree from a DOM tree. The larger and more complex the DOM, the more expensive this operation may be.

An important technique for keeping a page snappy is to batch methods that manipulate the DOM separately from those that query the state. For example:

// Suboptimal, causes layout twice.
var newWidth = aDiv.offsetWidth + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.height = newHeight + 'px'; // Write

// Better, only one layout.
var newWidth = aDiv.offsetWidth + 10; // Read
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
aDiv.style.height = newHeight + 'px'; // Write

Stoyan Stefanov's tome on repaint, relayout and restyle provides an excellent explanation of the topic.

This often leaves developers asking the question: What triggers layout? Last week Dimitri Glazkov answered this question with this codesearch link. Trying to understand it better myself, I went through and translated it into a list of properties and methods. Here they are:

Element

clientHeight, clientLeft, clientTop, clientWidth, focus(), getBoundingClientRect(), getClientRects(), innerText, offsetHeight, offsetLeft, offsetParent, offsetTop, offsetWidth, outerText, scrollByLines(), scrollByPages(), scrollHeight, scrollIntoView(), scrollIntoViewIfNeeded(), scrollLeft, scrollTop, scrollWidth

Frame, Image

height, width

Range

getBoundingClientRect(), getClientRects()

SVGLocatable

computeCTM(), getBBox()

SVGTextContent

getCharNumAtPosition(), getComputedTextLength(), getEndPositionOfChar(), getExtentOfChar(), getNumberOfChars(), getRotationOfChar(), getStartPositionOfChar(), getSubStringLength(), selectSubString()

SVGUse

instanceRoot

window

getComputedStyle(), scrollBy(), scrollTo(), scrollX, scrollY, webkitConvertPointFromNodeToPage(), webkitConvertPointFromPageToNode()

This list is almost certainly not complete, but it is a good start. The best way to check for over-layout is to watch for the purple layout bars in the Timeline panel of Chrome or Safari's Inspector.

11 comments:

Morgan Cheng said...

// Suboptimal, causes layout twice.
var newWidth = aDiv.offsetWidth + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.height = newHeight + 'px'; // Write

I suppose this triggers two reflow because the 3rd statements have reflow-queue flushed.

The Nerdbirder said...

@Morgan Cheng - Right. The methods listed require layout to be up to date. If layout is already up to date, then it is a no-op. Other methods which manipulate DOM/styles cause layout to be "dirty" meaning that it needs to be performed at some point but not necessarily immediately. So each line labeled "Read" requires an up-to-date layout and each "Write" dirties layout.

In this case the third line forces a layout and then another layout would occur after the script finishes.

Anonymous said...

Will the layout cause a visible repaint or is this done internally and not repainted until the script finishes?

I'm asking because in webkit i've never been able to cause flicker by ordering property-setting wrong.

In IE on the other hand you can get really nasty flicker when setting properties. Especially scrollLeft and scrollTop is a big problem as they can't be set simultaneously without a repaint between.

Anonymous said...

JavaScript doesn't necessarily run on a bytecode interpreter, FYI.

Anonymous said...

be happy and love. kiss

Anonymous said...

Wow this is a great resource.. I’m enjoying it.. good article

Matt said...

For anyone who's tried the codesearch link since, it's been moved.

Pauan said...

I would like to note that merely calling "getComputedStyle" doesn't seem to trigger a relayout. You have to actually lookup a property on the returned object, like this:

getComputedStyle(element).left

Eloise Turnbull said...

A list of complicated codes for a programs. One thing's for sure, with the use of this many businesses or people will be benefited from the process.

Beth said...

What are the best resources for learning about how browsers work internally? For instance, Tony said: "Other methods which manipulate DOM/styles cause layout to be "dirty"...". How do you find out how all of this works inside the browser? Are you reading the source code? Or is there documentation? Thanks!

Derek D said...

Great post, much appreciate the time you took to write this.