Andreas Grabner About the Author

Andreas Grabner has been helping companies improve their application performance for 15+ years. He is a regular contributor within Web Performance and DevOps communities and a prolific speaker at user groups and conferences around the world. Reach him at @grabnerandi

The Real Performance Overhead of CSS Expressions

Steve Souders wrote this in Best Practices for Speeding up Your Web Site regarding CSS Expressions:“CSS expressions are a powerful (and dangerous) way to set CSS properties dynamically”
… and …
“The problem with expressions is that they are evaluated more frequently than most people expect”

Last week I worked with a user of the dynaTrace AJAX Edition. Their team has done some performance investigations with CSS Expressions and came up with an interesting discovery which totally backs Steve’s comments. They created a simple HTML file containing 3 DIV tags using CSS Expressions to set the width of all DIVs to an appropriate value depending on the size of the browser. In order to analyze how often the CSS Expression is actually called they implemented a JavaScript counter to display this value in a text field. Here is their original version of this document:

<html><head>
<script>
var count = 0;var counterDisp;
function dispCounter(){ 
 if(!counterDisp){
  counterDisp = document.getElementById('exprCounter');
 }
 if(counterDisp){
  counterDisp.value = count++;
 }
}
</script>

<style>
  div { width:expression(dispCounter(), (document.body.clientWidth>500?"500px":"auto")); }
</style>
</head>
<body>
  No. of expression evaluations:&nbsp;<input type=text id=exprCounter size=10 disabled>
  <br><br>
  <div border=1 align=justify>
    Quoting Steve Souders, "The problem with expressions is that they are evaluated more
    frequently than most people expect. Not only are they evaluated when the page is
    rendered and resized, but also when the page is scrolled and even when the user moves
    the mouse over the page. Adding a counter to the CSS expression allows us to keep track
    of when and how often a CSS expression is evaluated. Moving the mouse around the page
    can easily generate more than 10,000 evaluations."
  </div><br>
  ....
  added the above div with the same content 3 times
  ...
</body>
</html>

As Steve says in his article – CSS Expressions are evaluated very often – especially when scrolling or moving your mouse over the document. Using the dynaTrace AJAX Edition on this document and moving the mouse over the document showed that IE is constantly re-evaluating the CSS expression, recalculating the layout and redrawing the page – for every pixel the mouse moves:

Constant Rendering Activity while moving the mouse

Constant Rendering Activity while moving the mouse

Not only does the browser re-evaluate the CSS Expression – it also needs to call the JavaScript expression every time it is re-evaluating it. Looking at the PurePath shows exactly what happens:

PurePath showing Rendering Activity and JavaScript Expression Calls

PurePath showing Rendering Activity and JavaScript Expression Calls

We can see that for every mouse pixel move Internet Explorer starts re-evaluating the expression. As we have 3 DIV tags it calls the JavaScript expression 3 times for every evaluation. Although the end-user doesn’t “feel” the extra overhead we can see that it consumes a lot of CPU and executes a lot of unnecessary JavaScript.

Revised Expression – extracting the expression to a JavaScript method

In the next iteration the CSS Expression was slightly modified. Instead of having the expression for the actual width value inline we call a JavaScript method. Here is the modified JavaScript and CSS Expression:

<script>
var count = 0;
var counterDisp;
function dispCounter(){ 
 if(!counterDisp){
  counterDisp = document.getElementById('exprCounter');
 }
 if(counterDisp){
  counterDisp.value = ++count;
 }
}
function setDivWidth(elem){
 elem.style.width = document.body.clientWidth>500?"500px":"auto"; 
}
</script>
<style>
div { width:expression(dispCounter(), setDivWidth(this)); }
</style>

This change did not affect the re-evaluation – we still see a CSS Evaluation for every mouse move. However – for a reason that I cannot yet explain – we do not see a call to dispCounter for every CSS Evaluation. Maybe someone out there has an idea of why that is. We also have much less CPU overhead which I believe is caused by the missing calls to dispCounter. Let’s have a quick look at the TimeLine and the PurePath showing the rendering activities:

Rendering Activity for every Mouse Move but lessCPU Usage

Rendering Activity for every Mouse Move but lessCPU Usage

PurePath showing viewer JavaScript activity triggered by Rendering

PurePath showing viewer JavaScript activity triggered by Rendering

Final Solution – setting width by JavaScript

In order to avoid all CSS Evaluations triggered by the CSS Expressions you can use a JavaScript approach. To correct way to do it is to have a JavaScript resize handler. We removed the style definition from the file and added the following script tag at the end of the document. Here is the final version:

<script language="javascript">
function setDivWidths() {
 var divs = document.getElementsByTagName("div");
 var divlength = divs.length;
 for(i=0;i<divlength;i++) {
  setDivWidth(divs[i]);
 }
 dispCounter();
}
window.onresize = setDivWidths;
setDivWidths();
</script>

The result of this is that we set the width once when the page is initially rendered and whenever the user resizes the window. There are no CSS Evaluations while moving with the mouse. Here is the timeline view showing the activity while being on the page and moving the mouse. In the end – I resized the window to test the resize event handler:

Eliminated unnecessary CSS Evaluations by using resize JavaScript handler

Eliminated unnecessary CSS Evaluations by using resize JavaScript handler

Call to Action

I hope this was useful information for all of you. I am interested in your thoughts on this problem and whether you also came to similar conclusions. There might be other ways to solve this problem – so – please keep the suggestions and feedback coming.

Special Thanks to the guys who showed me this sample – it was a pleasure working with you on this.


Comments

  1. Good article. Have you tried this with other commonly used expressions (like alphaimageloader or opacity)? I’m curious if they evaluate as often.

  2. @Will:havent tried that yet-but thanks for the suggestion. if you have any sample pages let me know

  3. Will: those are filters, not expressions. Still probably should be tested, but it’s a different system and I’d be surprised if they evaluated JS every pixel of mouse move.

  4. Great analysis,thanks for the article, IE expressions are indeed a performance bottleneck, glad to see that IE8 doesn’t stopped supporting expressions. By the way Márton Sári wrote one article “One time CSS expressions” on this topic, which basically caches the result of the expressions to avoid repeated evaluation. Here’s a link to that article:

    http://vacskamati.blogspot.com/2008/10/one-time-execution-of-ie-css.html

    Hope it helps shed some lights on it, Thanks.

  5. Do the IE team read this? this the kind of optimisation they should be adding to the browser.

  6. Excellent work, this information will improve understanding of the “pitfalls”. Thanks

  7. Eric TF Bat says:

    Excellent – another piece of ammunition in the war against IE6. IE6 has to use expressions for stuff that modern browsers, even IE7, can do natively in CSS. So this analysis helps in the long, slow process of convincing management that it’s time to ditch support for ten-year-old software. It won’t convince them by itself, but every little bit helps.

  8. You can simple turn off multiple executions of expression like this:
    element.runtimeStyle.behavior = “none”;

    So you could for example write something like this:

    function fixWidth(el) {
    dispCounter();
    // stop the CSS expression from being endlessly evaluated
    el.runtimeStyle.behavior = “none”;
    document.body.clientWidth>500 ? el.runtimeStyle.width = “500px” : el.runtimeStyle.width = “auto”;
    }

    And style div like this:

    div {
    behavior: expression(fixWidth(this));
    }

    This way, expressions will only be executed three times.

    See also example for PNG fix:
    http://www.tjkdesign.com/articles/png-alpha-transparency_with-no-http-request.asp

  9. @Boris
    Thanks for the suggestion. I tried it and analyzed it with the dynaTrace AJAX Edition. It shows the same CSS Re-evaluation behavior once I start moving the mouse. I verified that on my IE7. Have you tried this on different versions of IE?
    Thanks

  10. I’ve tested in only on Vista (IE 8 installed) using IETester v.0.4.2(both IE 6 and 7 instances) so I don’t know how it works in native browser.

    Also Dean Edwards has been using that style long time ago. I can’t access his blog, so here is cached version of blog post:

    http://74.125.77.132/search?q=cache:8d9ldygCGk8J:dean.edwards.name/weblog/2005/06/base64-sexy/+ie+base64+sexy+version

    I will try later in virtual machine with WinXP.

  11. Also, did you correctly copy pasted code. Watch out for “”.
    Just a thought :-)

  12. @Boris: yep – i ran into the copy/paste error in the beginning :-)
    I am not sure how detailed IETester is. dynaTrace AJAX actually hooks the Rendering Engine of IE – therefore we get more information than most of the other tools. I can totally see why the Rendering engine still is evaluating the expression – even though it does not call the JavaScript method anymore it still needs to iterate through all CSS Expressions internally.
    Let me know your findings once you have time testing it on other systems
    Thanks for your input!!

  13. @Andreas – I haven’t setup a sample, but plan to.

    @Robin – to clarify: alphaimageloader and opacity are commonly used in CSS expressions.

  14. I found a nice sample at: http://perishablepress.com/press/2008/05/28/css-hackz-series-png-fix-for-internet-explorer/
    .
    .png {
    position:relative;
    behavior:expression((this.runtimeStyle.behavior=”none”)&&(this.pngSet?this.pngSet=true:(this.nodeName == “IMG” && this.src.toLowerCase().indexOf(‘.png’)>-1?(this.runtimeStyle.backgroundImage = “none”,
    this.runtimeStyle.filter = “progid:DXImageTransform.Microsoft.AlphaImageLoader(src=’” + this.src + “‘, sizingMethod=’image’)”,
    this.src = “transparent.gif”):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace(‘url(“‘,”).replace(‘”)’,”),
    this.runtimeStyle.filter = “progid:DXImageTransform.Microsoft.AlphaImageLoader(src=’” + this.origBg + “‘, sizingMethod=’crop’)”,
    this.runtimeStyle.backgroundImage = “none”)),this.pngSet=true));
    }

  15. @Andreas
    I’ve tested in WinXP virtual machine (again, IE8 is default browser) using DynaTrace Ajax Edition and it is still evaluating css expressions.
    Also, I’ve played with removeExpression function but with no real effect.
    On every: “Rendering (Drawing)” I see about 50 – 100 new “Rendering (Evaluating CSS expressions)”.

    Maybe I’ll use this technique only for IE6 (in some sort of generic stylesheet).

  16. @Will & @Boris
    Thanks for your input – thats great!
    I am sure all our readers appreciate the input.

  17. As mentioned above by Arnab C, who linked to my article, one-time execution of CSS expressions is a solved issue.

    The only thing I would add to this here is that therefore you can put virtually any initialization javascript code in CSS expressions without repeated execution.

    Not as i could mention any valid case for putting complex code in expressions, but for CSS workarounds targeted for IE, i think, CSS expressions are a good place.

  18. IE expressions are great to fix support for various things like hover (using exclusive onmouseenter/onmouseleave) or focus on bulk of elements. Mostly I use one time run expressions via redefining attribute by runtimeStyle and I noticed that expressions run many times when there are multiple selector. E.g. in .class1, .class2, .class3 { behavior: expression(…) } expressions executing constantly, but when I used only one selector expression was executed only one time per element (zoom is used for better combining):
    .input_text {
    //zoom: expression(
    function(t){
    t.runtimeStyle.zoom = ‘normal’;
    t.attachEvent(‘onfocus’,function() { t.className += ‘ focus’ });
    t.attachEvent(‘onblur’,function() { t.className = t.className.replace(‘ focus’, ”) });
    }(this)
    );
    }

  19. This is really a very good piece of writing as it can clarify lots of doubt in the mind of css coders. Again I’ve a doubt. setting of a width can be done javascript but I’d like to see the coding for the following css expresion written for png images in ie6.

    position:relative;
    behavior: expression((this.runtimeStyle.behavior=”none”)&&(this.pngSet?this.pngSet=true:(this.nodeName == “IMG” && this.src.toLowerCase().indexOf(‘.png’)>-1?(this.runtimeStyle.backgroundImage = “none”,
    this.runtimeStyle.filter = “progid:DXImageTransform.Microsoft.AlphaImageLoader(src=’” + this.src + “‘, sizingMethod=’image’)”,
    this.src = “spacer.gif”):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace(‘url(“‘,”).replace(‘”)’,”),
    this.runtimeStyle.filter = “progid:DXImageTransform.Microsoft.AlphaImageLoader(src=’” + this.origBg + “‘, sizingMethod=’crop’)”,
    this.runtimeStyle.backgroundImage = “none”)),this.pngSet=true));

Comments

*


8 − = one