While trying to reduce the HTML size of a web page, I've come across suggestions by Google and the PageSpeed Firefox Add-On regading efficiency of CSS selectors that (almost) made me reconsider the changes:
http://code.google.com/intl/de-DE/speed/page-speed/docs/rendering.html#UseEfficientCSSSelectors
Specifically, descendant selectors are great for selecting a whole block (e.g. DIV) using an ID or CLASS attribute and then keeping all of its child elements free of CLASS/ID attributes. But if the order of traversal for applying the rules is as described by Google, they should not be used:
Descendant selectors are inefficient because, for each element that matches the key, the browser must also traverse up the DOM tree, evaluating every ancestor element until it finds a match or reaches the root element. The less specific the key, the greater the number of nodes that need to be evaluated.
I very much doubt that browsers use such an inefficient order of traversal, surely they would only process subtrees of elements that match the top selector component, i.e. in #foo span {...}
only elements below #foo should be checked and not every single span. Can anyone who has looked at recent browser code confirm/deny this?
The second questionable suggestion is about overly qualified selectors:
ID selectors are unique by definition. Including tag or class qualifiers just adds redundant information that needs to be evaluated needlessly.
If ID selectors are unique by definition, why would browsers need to check the redundant information? I know that they do because for example,
div#foo { color: black; } #foo { color: white; }
will result in black text in a <div id=foo>
, but a) it should not be done(? W3C reference needed) and b) I don't see why it would be noticeably slower when it results in a simple O(1) check of the element's tag name.
Can anyone on good terms with modern browsers' source code shed some light on these claims? Since most modern sites use descendant selectors (including SO) and they have clear advantages, I'd very much like to use them...
Edit:
I've experimented a bit with generated pages and it seems that the browsers' handling of descendant selectors is indeed pitiful:
A page consisting of (abbreviated):
#top a {text-decoration: none;}
#foo1 a.foo {color: red;}
#foo2 a.foo {color: red;}
[... repeated 10000 times]
<body id=top>
<div>...[nested 50 times]<a href=foo>bla</a></div>[...]
[previous line repeated 10000 times]
(basically 10000 lines with 50 nested divs each to traverse till the root node and 1 selector that matches out of 10000)
loads and renders (time till window.onload()
executes) in 2.2 seconds using Safari 5 and just under 10 seconds with Firefox 3.6.10.
When the .foo
class selector is removed from the non-applying rules, the page takes around 200 seconds with Safari 5 and 96 seconds with Firefox 3.6.10. This illustrates how badly the descendant selectors are implemented (in that case, each of the 10000 rules probably causes a traversal till #top, where the rule fails).
How do child selectors fare? #foo > span > div > div > div > div > div a {color: red;}
(also never matches but forces traversal of 6 parent nodes) takes 27 seconds with Safari 5 and 31 seconds with Firefox 3.6.10.
Conclusion
Descendant and child selectors both suck currently on major browsers. It's still better to add ugly class/id attributes to all your styled tags if you care about speed, at least for very common HTML tags (such as a, img, div etc.).
Have a look at this recent post by Jonathan Snook: http://snook.ca/archives/html_and_css/css-parent-selectors
You'll see how browsers evaluate expressions and the reasoning behind why certain selectors are inefficient.
A relevant quote from the post:
CSS gets evaluated from right to left.
To determine whether a CSS rule applies to a particular element, it starts from the right of the rule and works it's way left.
If you have a rule like body div#content p { color: #003366; } then for every element—as it gets rendered to the page—it'll first ask if it's a paragraph element. If it is, it'll work its way up the DOM and ask if it's a div with an ID of content. If it finds what it's looking for, it'll continue its way up the DOM until it reaches the body.
By working right to left, the browser can determine whether a rule applies to this particular element that it is trying to paint to the viewport much faster. To determine which rule is more or less performant, you need to figure out how many nodes need to be evaluated to determine whether a style can be applied to an element.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With