The
note by Vladimir Tokmakov states:
The HTML element in the document can be hidden using JavaScript or the CSS display property. The logic embedded in CSS is difficult and unnecessary to reproduce in JavaScript. It's easier to query the offsetHeight object (if 0 = the item is hidden).
Easier, of course, simpler, but what is the price?
To check the visibility of an element, it is customary to check the
display
style value or the presence of a
hide
class. When we write the function of hiding / displaying ourselves, we know which value of the default
display
style of the object, or which class corresponds to which state. However, the universal (library) function cannot be aware of this.
Let's test the speed of calculating the values ​​of
offsetHeight
and
style.display
.
For the convenience of profiling, we will move access to these values ​​into separate functions:
function fnOffset (el)
{
return !! el.offsetHeight;
}
function fnStyle (el)
{
return el.style.display == 'none';
}
Where
el
is a test container.
After testing a thousand iterations, we see that access to
offsetHeight
only two times slower than to
style.display
.
Now let's complicate the test - at each iteration we will add a
SPAN
element to the test container.
Profiling results:

Now the relationship between the test is completely different. Why did this happen? Let's check the time it takes to add thousands of elements, without calling the test functions — the "clean" test. We will conduct testing in all browsers, measuring the time in the following way:
var time_start = new Date (). getTime ();
/ * ... test ... * /
var time_stop = new Date (). getTime ();
var time_taken = time_stop-time_start;
Where
time_taken
is the time spent per test, in milliseconds.
Test data is given in milliseconds and the average is taken for 5 runs:

Judging by the test results, access to
offsetHeight
is 50-150 times slower.
It turns out that in terms of length and
offsetHeight
and adding elements works quickly, and together - very slowly. How so?
')
Why such a gap between the test?
A bit of theory.
Reflow is the process of recursively traversing a branch of a DOM tree that calculates the geometry of the elements and their position relative to the parent. The beginning of the traversal is a changed element, but it is also possible to spread from the bottom up. There are the following types of reflow:
- the initial is the first mapping of the tree;
- incremental - occurs when changes in the DOM;
- resizing;
- style change;
- "dirty" - the union of several incremental reflow having a common parent.
Reflow are divided into
urgent (resizing windows or changing the font of the document) and
asynchronous , which can be postponed and merged later.
When manipulating DOM, incremental reflows occur, which the browser defers until the end of the script. However, based on the definition of a reflow, the “measurement” of the element will force the browser to perform deferred reflow. it is possible to propagate from bottom to top, all reflow is performed, even if the element being measured belongs to the unchanged branch.
Reflow is very resource intensive and is one of the reasons for slowing down web applications.
Judging by the test "clean", all browsers do a good job with caching numerous reflow. However, by requesting
offsetHeight
, we "measure" the element, which causes the browser to perform deferred reflow. Thus, the browser does a thousand reflow in one case and only one in the other.
Note: Opera's reflow also runs on a timer, which, however, does not prevent it from passing the test faster than other browsers. Due to this, the course of the tests is visible in the Opera - the added stars appear. Such behavior is justified, since causes the user to feel a faster browser speed.
Summarize. What did the testing show? At least, it is incorrect to compare the universal (
offsetHeight
) and private (
style.display
) cases. Testing has shown that you have to pay for universality.
And if you still want versatility, then you can offer another approach - the definition of Computed Style - the final style of the element (after all the CSS transformations).
getStyle = function ()
{
var view = document.defaultView;
if (view && view.getComputedStyle)
return function getStyle (el, property)
{
return view.getComputedStyle (el, null) [property] || el.style [property];
};
return function getStyle (el, property)
{
return el.currentStyle && el.currentStyle [property] || el.style [property];
};
} ();
We will test this method and put all the results in a table.

In IE and FF, the computed style is calculated as quickly as the style of the element itself, and in Opera and Safari even a slightly longer
offsetHeight
. The cited article clearly states that the call to
getComputedStyle
also causes reflow, and the reasons for the lack of this in IE and FF are not clear, although they are encouraging.
UPD: early rejoiced;) Thanks to
AKS for pointing out that
getComputedStyle
in IE and FF returns incorrect results.
When searching the Internet for ways to optimize computed style calculations for Opera and Safari, the article
Computed vs Cascaded Style was found, in which Erik Arvidsson recommends not using such universal functions (
getStyle
is in almost every js library), and implement the necessary functionality in each case . After all, if we agree that the hidden elements must have a
hide
class, then everything will be reduced to determining whether the element or its parents have this class.
Optimization: class definition hide
Let's take a closer look at the solution I proposed. I suggest the following implementation:
function isHidden (el)
{
var p = el;
var b = document.body;
var re = / (^ | \ s) hide ($ | \ s) /;
while (p && p! = b &&! re.test (p.className))
p = p.parentNode;
return !! p && p! = b;
}
It is assumed that it does not make sense to hide the root elements of the DOM and therefore checks are carried out only until
document.body
.
The proposed solution will obviously not lower the reflow avalanche. no calculations and measurements are carried out. However, the traverse to the root of the document is a little confusing: what happens if the elements are nested? Let's check. The isHidden test is performed for nesting 2 (
document.body / test_div
). And the isHidden2 test for nesting is 10 (
document.body / div * 8 / test_div
).

As the tests show, even with a large nesting speed drop is small. Thus, we got a universal solution that is 30-100 times faster to access offsetHeight.
This article is intended not so much to solve the problem of determining the visibility of an element in the general case, but rather to explain one of the most frequently encountered bottlenecks of interaction with DOM and a detailed analysis of optimization methods. During the tests, I deliberately reproduced the worst case. In real situations, such an increase in speed will be obtained only when used in animation. However, understanding the reasons and the reflow mechanism will allow writing more optimal code.
References:
- Note by Vladimir Tokmakov, which was the reason for writing this article.
- Article Efficient JavaScript , which describes how to optimize JavaScript, and in particular how to minimize the amount of reflow.
- Article Notes on HTML reflow , which describes the nuances of the implementation of reflow in Gecko.
- Article Computed vs Cascaded Style which addresses the shortcomings of getStyle functions
Tests:
for all browsers ,
for profiling in FireBug .
Ps. Thanks to
AKS for the comments that made me more clearly define the definition of reflow.