Why does innerText require layout? I ended with that question in my previous post about layout thrashing. Just to recap briefly, there are very common patterns of use in the DOM APIs that cause terrible performance due to unnecessary layout. These posts highlight some of the oddities that I’ve found working on web performance for several years.
As with many other things in browsers, innerText’s behavior seemed to have happened due to overlapping (and, perhaps, under-defined) use cases. When you ask for the text contained within some DOM tree, you may be asking one of two questions: What is the raw textual content inside of these Nodes? Or, what is the text being presented to the user? These are similar, but obviously different. In a browser, textContent gives you the former and innerText the latter.
To illustrate some of the key differences, here is an example. Let us look at both innerText and textContent of the following HTML.
Notice the differences, (1) the elements that are not rendered are also not present in innerText and (2) the line breaks in innerText follow the line breaks that were introduced by layout (not the original text we stuffed in the DOM). The best way to think about innerText is that it is roughly what you would get if you selected the text and copied. Whereas, textContent is just a concatenation of the values of all TextNodes in the sub-tree.
The key takeaway is that innerText requires some information from the layout system to determine how the text is being presented to the user. This is what makes innerText one of those properties that can cause the performance of your app to go off the rails. Most libraries that favor innerText over textContent do so accidentally only because innerText was in Internet Explorer before textContent arrived as a properly specified API. For completeness, let me demonstrate the impact that choosing innerText over textContent can have on performance.
On a WebKit browser, you should see a significant performance difference (~300ms vs ~1ms). On IE9, you’ll see better performance and a much smaller difference. It is clear that IE avoids computing a full layout and probably uses a special code path that computes only what is needed for innerText (which really isn’t much). If you are using Firefox or Opera, you may be scatching your head. Keep reading.
While one could certainly conceive of use cases for innerText, most callers just assume that innerText and textContent are identical. You will see the expression node.innerText || node.textContent still being used in a number of libraries. Unfortunately, that leaves the door open for some unexpected performance problems. It is much wiser to prefer textContent these days.
While it is still widely used, innerText is not standard. It is a bit of behavior that has lived on due to wide use during the Internet Explorer era. It’s heavy use back then is probably the reason IE seems to have a specialized code path. To this day, it is not present in Firefox (wise decision on their part) and its behavior still varies widely in the browsers that do support it. Opera, for instance, merely computes textContent when you try to access innerText. This is why it outperforms WebKit in the example I show. When I use the expression “browser landmine”, innerText is what I have in mind. To quote my good friend, Joel Webber, “it’s slower, but at least it doesn’t work as you would expect.”