Late last year, I started to use the direct child CSS selector much more frequently in my modular patterns, and naturally became curious if I was causing harm to my users who had to download this code. I began doing research on CSS performance after becoming interested in best practices for writing more efficient code. I found some Mozilla documentation with recommended best practices and a few blog posts on the subject. Most of these said that what I was doing was less efficient and should be avoided.
I then investigated methods of testing CSS performance. One of these testing methods was to build out a huge page with thousands of DOM elements and CSS selectors to see how fast the page could be loaded. Loosely following the testing methods I read about and the Mozilla recommended guidelines, I decided to do my own test to see how big the difference was between different types of selectors.
I created two PHP files, one to generate my DOM with unique elements and another to generate my matching CSS. I created files for 1k, 2k, 3k, 4k, 5k, 10k, 15k, & 20k elements on the page with their matching css files. All tests were performed in Google Chrome and files were stored locally to prevent network latency from being a factor.
My first test was to see how long it took my browser to load each CSS file. I performed each test ten times, using Chrome’s built-in developer tools to determine how long it took the browser to download and load each CSS file.
What I discovered from the first test was that file download times seemed mostly linear, which made perfect sense. The larger the file was, the longer it took the browser to load the file.
To continue my testing, I wanted to know how different the render times would be if I used unique classes, child selectors, and descendant selectors.
My DOM looked like this in every test:
<div class=”element-1”>
<div class=”element-2”>
<div class=”element-3”>
</div>
</div>
</div>
The second test matched each element by it’s unique class name. This should be the most efficient method of matching elements, and the fastest. The third test matched by descendant selectors (.element-1 .element-2 .element-3). And the final test matched by direct child selectors (.element-1 > .element-2 > .element-3). This should be the most expensive selector, since the browser needs to determine if each element is a direct child of the previous element.
These graphs shows that each method of element selection tends to have a mostly linear page load time with a trend to be slightly under the line.
When combining the three graphs together, we can see that each method of element selection is comparable when pages fall on the lower end of DOM elements. We only begin to see a separation in lines when we reach the 10k or above mark.
The takeaway for me is that creating CSS patterns that make sense to you and are reuseable in your codebase is much more important than worrying about creating perfect selectors. Computers have gotten faster and browsers get better at matching CSS rules with each update, and there is much more low-hanging fruit that you can pick to get performance gains out of your front-end.