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.