Is declaring "content" property on :before and :after for every element a massive performance issue?
Asked Answered
P

2

8

As you probably know, if you will to use :before and/or :after pseudoelements without setting text in it, you still have to declare content: ''; on them to make them visible.

I just added the following to my base stylesheet :

*:before, *:after {
    content: '';
}

...so I don't have to declare it anymore further.

Apart from the fact the * selector is counter-performant, which I'm aware of (let's say the above is an example and I can find a better way to declare this, such as listing the tags instead), is this going to really slow things down ? I don't notice anything visually on my current project, but I'd like to be sure this is safe to use before I stick it definitely into my base stylesheet I'm going to use for every project...

Has anyone tested this deeply ? What do you have to say about it ?

(BTW, I do know the correct CSS3 syntax uses double semicolons (::before, ::after) as these are pseudoelements and not pseudoclasses.)

Pulitzer answered 8/10, 2013 at 13:33 Comment(0)
P
7

So I ran some tests based on @SWilk's advice. Here's how I did it :

1) Set up a basic HTML page with an empty <style> tag in the <head> and the simple example he provided in a <script> tag at the bottom of the <body> :

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Performance test</title>

    <style>
/**/
    </style>
</head>

<body onload="onLoad()">
    <div class="container"></div>

    <script>
function onLoad() {
    var now = new Date().getTime();
    var page_load_time = now - performance.timing.navigationStart;
    console.log("User-perceived page loading time: " + page_load_time);
}
    </script>
</body>

</html>

2) Fill up the div.container with loaaads of HTML. In my case, I went to html-ipsum.com (no advertising intended), copied each sample, minified it all together, and duplicated it a bunch of times. My final HTML file was 1.70 MB, and the div.container had 33264 descendants (direct or not ; I found out by calling console.log(document.querySelectorAll('.container *').length);).

3) I ran this page 10 times in the latest Firefox and Chrome, each time with an empty cache.

Here are the results without the dreaded CSS ruleset (in ms) :

Firefox :
1785
1503
1435
1551
1526
1429
1754
1526
2009
1486
Average : 1600

Chrome :
1102
1046
1073
1028
1038
1026
1011
1016
1035
985
Average : 1036

(If you're wondering why there's such a difference between those two, I have much more extensions on Firefox. I let them on because I thought it would be interesting to diversify the testing environments even more.)

4) Add the CSS we want to test in the empty <style> tag :

html:before, html:after,
body:before, body:after,
div:before, div:after,
p:before, p:after,
ul:before, ul:after,
li:before, li:after,
h1:before, div:after,
strong:before, strong:after,
em:before, em:after,
code:before, code:after,
h2:before, div:after,
ol:before, ol:after,
blockquote:before, blockquote:after,
h3:before, div:after,
pre:before, pre:after,
form:before, form:after,
label:before, label:after,
input:before, input:after,
table:before, table:after,
thead:before, thead:after,
tbody:before, tbody:after,
tr:before, tr:after,
th:before, th:after,
td:before, td:after,
dl:before, dl:after,
dt:before, dt:after,
dd:before, dd:after,
nav:before, nav:after {
    content: '';
}

...and start again. Here I'm specifying every tag used in the page, instead of * (since it is counter-performant in itself, and we want to monitor the pseudo-element triggering only).

So, here are the results with all pseudo-elements triggered (still in ms) :

Firefox :
1608
1885
1882
2035
2046
1987
2049
2376
1959
2160
Average : 1999

Chrome :
1517
1594
1582
1556
1548
1545
1553
1525
1542
1537
Average : 1550

According to these numbers, we can conclude the page load is indeed slower (of about 400-500 ms) when declaring content: '' on every pseudo-element.

Now, the remaining question now is : is the extra load time we can see here significative, given the relatively big test page that was used ? I guess it depends on the size of the website/project, but I'll let more web-performance-knowledgeable people give their opinion here, if they want to.

If you run your own tests, feel free to post your conclusions here as well, as I'm very interested in reading them - and I think I won't be the only one.

Pulitzer answered 8/10, 2013 at 16:42 Comment(8)
The explanation that comes to mind is that this will insert a pseudo-element before and after every tag, including the ones that otherwise wouldn't need them (which, in this test, is every tag). It's basically tripling the number of elements on the page.Addieaddiego
@Addieaddiego That's a good point, but my thoughts are that it could be browser-dependent, since the *:before and *:after rules are technically just styling rules, not pseudoelement creation rules (in theory). Perhaps some browsers always create the two hidden pseudoelements with every real element on the page, so the CSS rules wouldn't cause the issue you pointed out. On the other hand, other browsers may only reactively create the pseudoelements if style rules target them, in which case one indeed is creating excess pseudoelements on all real elements with those rules.Boule
I also wonder if individual tag targeting (as opposed to using *) would not allow for certain optimizing assumptions to be made by the browser, since it now has to check each tag against the list of styles, rather than knowing a * rule will apply to any tag no matter what. Also, I think it would be more fair to FF if you did it without extensions/add-ons. There's no telling what kind of behind-the-scenes things the extensions do to the page, so your FF statistics may be muddied by them.Boule
@ajp15243: They may technically be just styling rules, but "don't bother including pseudoelements with no styling" strikes me as a no-brainer of a browser optimization.Addieaddiego
@Addieaddiego I agree, but I've seen dumber things :).Boule
Guess it could save time while writing your CSS, then you use some Grunt magic to remove the * (or element-by-element) rule and add content: ''; to all :before and :after rules which don't already have this property declared. Does it sound doable ? I don't use Grunt and the likes yet.Pulitzer
For the record, setting *:before, *:after { content: ''; } causes unexpected behavior on current Chrome, so it's definitely a no-go.Pulitzer
I have observed a drastic performance decrease in IE8 when using these pseudo-elements to apply CSS to a large number of HTML elements. Unfortunately I have no hard data to support this statement. What I can say though is that even scrolling through such a page was very slow and choppy.Worrisome
R
2

My answer is: I don't know, but one can test that.

Read about Navigation Timing and check out this example page

The most simple example of usage:

function onLoad() { 
  var now = new Date().getTime();
  var page_load_time = now - performance.timing.navigationStart;
  console.log("User-perceived page loading time: " + page_load_time);
}

So, go generate a few MB of html full of random tags, and test.

Racketeer answered 8/10, 2013 at 13:59 Comment(2)
Thanks ! I'll run some tests and post my results.Pulitzer
@neemzy: awaiting your results information. It would be good to know such things.Pember

© 2022 - 2024 — McMap. All rights reserved.