Eoin
Eoin

Reputation: 1515

CSS Performance, #id vs .class vs [^regex$] vs selector

I have seen that it is a performance hit to do something like:

a[rel^=ext] {

Which makes sense. But I have also found a bit of code in a template I used that does this for each section on the site:

@print {
 #section-name {
  color: #000;
  background: #fff;
 }
} 

I figured I could just use [^] to select them all and making one rule and saving lines of code. Then I found out that would be a performance hit.

So I checked and discovered that there is an outer div with an id. So then I thought I could do #idName section.

But that uses an element, so again would probably be a performance hit I assume.

Does anyone have any information where I can find out more about performance and which way would be the quickest. e.g. is the performance hit on a bigger file worse or the computation of many selectors worse?

As a further part, I find this sort of thing very interesting, does anyone have a good, reliable way to test these things? Using online services gives a different result each time, so would require thousands of goes to make good numbers. Does anyone know a good way to undertake these actions?

Upvotes: 1

Views: 1006

Answers (1)

disinfor
disinfor

Reputation: 11558

Per Eoin's request, I'm making an answer for future visitors (as I think this is useful).

From this link: https://www.sitepoint.com/optimizing-css-id-selectors-and-other-myths/

The following snippet runs on 50,000 nodes. The console output will give you an answer on performance for specific selectors.

const createFragment = html =>
  document.createRange().createContextualFragment(html);

const btn = document.querySelector(".btn");
const container = document.querySelector(".box-container");
const count = 50000;
const selectors = [
  "div",
  ".box",
  ".box > .title",
  ".box .title",
  ".box ~ .box",
  ".box + .box",
  ".box:last-of-type",
  ".box:nth-of-type(2n - 1)",
  ".box:not(:last-of-type)",
  ".box:not(:empty):last-of-type .title",
  ".box:nth-last-child(n+6) ~ div",

];
let domString = "";

const box = count => `
<div class="box">
  <div class="title">${count}</div>
</div>`;

btn.addEventListener("click", () => {
  console.log('-----\n');
  selectors.forEach(selector => {
    console.time(selector);
    document.querySelectorAll(selector);
    console.timeEnd(selector);
  });
});

for (let i = 0; i < count; i++) {
  domString += box(i + 1);
}

container.append(createFragment(domString));
body {
  font-family: sans-serif;
}

* {
  box-sizing: border-box;
}

.btn {
  background: #000;
  display: block;
  appearance: none;
  margin: 20px auto;
  color: #FFF;
  font-size: 24px;
  border: none;
  border-radius: 5px;
  padding: 10px 20px;
}

.box-container {
  background: #E0E0E0;
  display: flex;
  flex-wrap: wrap;
}

.box {
  background: #FFF;
  padding: 10px;
  width: 25%
}
<button class="btn">Measure</button>
<div class="box-container"></div>

From the sitepoint link as well, here is some more data with information to back it up:

The test was bumped up a bit, to 50000 elements, and you can test it out yourself. I did an average of 10 runs on my 2014 MacBook Pro, and what I got was the following:

  • Selector : Query Time (ms)
  • div : 4.8740
  • .box : 3.625
  • .box > .title : 4.4587
  • .box .title : 4.5161
  • .box ~ .box : 4.7082
  • .box + .box : 4.6611
  • .box:last-of-type : 3.944
  • .box:nth-of-type(2n - 1) : 16.8491
  • .box:not(:last-of-type) : 5.8947
  • .box:not(:empty):last-of-type .title : 8.0202
  • .box:nth-last-child(n+6) ~ div : 20.8710

The results will of course vary depending on whether you use querySelector or querySelectorAll, and the number of matching nodes on the page, but querySelectorAll comes closer to the real use case of CSS, which is targeting all matching elements.

Even in such an extreme case, with 50000 elements to match, and using some really insane selectors like the last one, we find that the slowest one is ~20ms, while the fastest is the simple class at ~3.5ms. Not really that much of a difference. In a realistic, more “tame” DOM, with around 1000–5000 nodes, you can expect those results to drop by a factor of 10, bringing them to sub-millisecond parsing speeds.

The takeaway from all this:

What we can see from this test is that it’s not really worth it to worry over CSS selector performance. Just don’t overdo it with pseudo selectors and really long selectors.

Another test here: https://benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/ covered data-attribute and regex selectors. It found:

  1. Data attribute
  2. Data attribute (qualified)
  3. Data attribute (unqualified but with value)
  4. Data attribute (qualified with value)
  5. Multiple data attributes (qualified with values)
  6. Solo pseudo selector (e.g. :after)
  7. Combined classes (e.g. class1.class2)
  8. Multiple classes
  9. Multiple classes with child selector
  10. Partial attribute matching (e.g. [class^=“wrap”])
  11. nth-child selector
  12. nth-child selector followed by another nth-child selector
  13. Insanity selection (all selections qualified, every class used e.g. div.wrapper > div.tagDiv > div.tagDiv.layer2 > ul.tagUL >
  14. li.tagLi > b.tagB > a.TagA.link)
  15. Slight insanity selection (e.g. .tagLi .tagB a.TagA.link)
  16. Universal selector
  17. Element single
  18. Element double
  19. Element treble
  20. Element treble with pseudo
  21. Single class

Here are the results. You should note they are from 2014 browsers. All times in milliseconds:

Test    Chrome 34   Firefox 29  Opera 19    IE9     Android 4
1       56.8        125.4       63.6        152.6   1455.2
2       55.4        128.4       61.4        141     1404.6
3       55          125.6       61.8        152.4   1363.4
4       54.8        129         63.2        147.4   1421.2
5       55.4        124.4       63.2        147.4   1411.2
6       60.6        138         58.4        162     1500.4
7       51.2        126.6       56.8        147.8   1453.8
8       48.8        127.4       56.2        150.2   1398.8
9       48.8        127.4       55.8        154.6   1348.4
10      52.2        129.4       58          172     1420.2
11      49          127.4       56.6        148.4   1352
12      50.6        127.2       58.4        146.2   1377.6
13      64.6        129.2       72.4        152.8   1461.2
14      50.2        129.8       54.8        154.6   1381.2
15      50          126.2       56.8        154.8   1351.6
16      49.2        127.6       56          149.2   1379.2
17      50.4        132.4       55          157.6   1386
18      49.2        128.8       58.6        154.2   1380.6
19      48.6        132.4       54.8        148.4   1349.6
20      50.4        128         55          149.8   1393.8
Biggest Diff.   
        16          13.6        17.6        31      152
Slowest         
        13          6           13          10      6

Upvotes: 2

Related Questions