CSS Selector Performance
Back in July 2014 I had the privilege to talk at the first MelbCSS meetup at 99Designs in front of a respectable crowd of front-end developers from beginners to the very experienced in the community.
At the end of my talk there was a common theme in the questions, almost all came back to the disbelief on how browsers interpret your CSS rules.
A common rule example
Consider
section#layout-rhs .module div {
/* styles */
}
I put a very similar CSS selector to the audience and asked for someone to guess what is the first interpretation of this rule, specifically, which elements in my DOM would be selected in the dataset of the browsers initial interpretation while parsing this rule.
Here are some of the answers;
- the child
dvi
element of element with id oflayout-rhs
- just the element with id of
layout-rhs
- All
section
elements - element with id of
layout-rhs
but only if it is asection
element
Unfortunately I had the expectation that the second or third answer would be correct when i asked this question so i was astonished to get 4 incorrect answers.
My talk was never originally meant to educate on how the browser interprets the CSS selector, as to me that seemed the most basic fundamentals of CSS. I intended to simply engage the audience in my talk with a simple question.
So being confused by the answers I presented a slide with the following phrase.
All CSS rules are read right to left
One person yelled out "no way", while another was giggling. A young woman in the front row had heard this before and as she stated it to the person beside I continued.
What was the first elements selected?
All of the
div
elements in the entire DOM
That may surprise some of you even today.
Impact
If anyone has spent time looking at performance audits you scoff at me for pointing out a single rule like the one above, and you would be right to say that on its own the difference between that and an #id
selector is trivial and measured in thousandths of a second - accumulate all of the CSS selectors of a decent modern app however and we are getting close to that 1 second difference just for not optimising your CSS selectors.
Knowing that CSS selectors are read from RIGHT to LEFT we now consider;
- The entire DOM is a factor.
- Complex rules add levels of computation.
- Unused CSS styles are evaluated.
- Anything to the left of an ID is irrelevant.
- Try to remove anything on the left of a good performing selector.
- Clever rules targeting something specific may not have been very smart after all.
- Yes, jQuery is also a consideration.
jQuery has it's own CSS selector subset, but it still interprets right to left. Native JavaScript CSS selectors are the significantly faster document.querySelector
and document.querySelectorAll
which leverages the browser CSS engine directly and therefore is subject to the rules set out for your CSS styles.
Preprocessors
I'm an everyday user of LESS these days, and i maintain one app using SCSS and have ventured into Stylus a few years back.
I am personally pro preprocessors. but they often work against you without you being the wiser.
Preprocessors are great productivity tools.
Consider LESS:
body {
table {
&.class1 {
tr td {
/* cell styles */
}
}
}
.class2 {
/* class styles */
&.active {
/* conditional styles */
}
}
}
Compiled:
body { }
body table.class1 { }
body table.class1 tr { }
body table.class1 tr td { }
body .class2 { }
body .class2.active { }
Preprocessors can offer great structure, and maintainability. But what is wrong here in terms of performance?
- Overly complex rules for the browser interpretation
- Multiple levels of computation due to the encapsulated classes
- Has simple vanilla CSS implementation
Order of efficiency
Selectors efficiency may not suite all use cases. Don't eliminate using pseudo selector in fear just optimise their implementation.
- ID - example: #unique
- Class - example: .reusable
- Tags - example: tr
- Adjacent sibling - example: tr + th
- Child - example: div > p
- Descendant - example: div a
- Attribute - example: [type="email"]
- Pseudo - example: :focus
Please don't go crazy and style ID's everywhere
Naming Conventions
The awesome front-end developer Hannah Thompson at Punters.com.au whom I work with was responcible for implementing BEM to our already very mature code base so that we could focus more on our components without the confusion of CSS and concern about performance.
I encourage you to use BEM, it will change the way you look back at CSS when debugging when you realise how simple it has become.
Conclusion
We've learned that browser read from right to left, and that nesting your preprocessor css is generally not a good idea so keep it 2 levels deep at most.
ID selectors are best, and close behind class selectors are your optimal choice and work in all naming conventions.
Enjoyed this?
If you made it here I would love to hear what you thought of the article.
Member discussion