CSS Expressions

CSS expressions were introduced in Internet Explorer 5.0 and it allows you to to assign a JavaScript expression to a CSS property. For example, this allows you to set the position of an element according to the browser size.

#myDiv {
   position:   absolute;
   width:      100px;
   height:     100px;
   left:       expression(document.body.offsetWidth  - 110 + "px");
   top:        expression(document.body.offsetHeight - 110 + "px");
   background: red;
}

Not the best way to achieve this but it is easy enough to understand.

Dynamic Expressions

Not only does CSS expressions allow you to calculate the CSS value inside the style declarations, it also recalculates the expression so that property value is always correct. This means that the expression needs to be recalculated every time the document.body.offsetWidth is changed. If this was the case dynamic expressions would actually be a lot more useful. This isn't the case and recalculations of the expressions happens every time a JS execution thread is finished. This means that if you move your mouse and have a listener to onmousemove all the dynamic expressions are recalculated. This does not require you to be a genious to figure out that this can bring the simplest web application to a crawl.

Take a look at the following CSS block:

#myDiv {
   border:  10px solid Red;
   width:   expression(ieBox ? "100px" : "80px");
}

Asuming ieBox is a constant flag which is true when IE is in quirks mode, the expression will at all times result in "80px" (or "100px"). Even though the expression is constant it will be recalculated a lot of times. The question is how to remove these extra calculations for constant expressions.

Constant Expressions

The thing to do is to go through all the style sheet declarations and replace the constant expressions with a constant value. In the previous example, assuming we are using IE6 in standard mode, we want:

#myDiv {
   border:  10px solid Red;
   width:   80px;
}

So, how do we find out if an expression is constant. The easiest way is to mark the expression so that it can be easily found. The solution we are going to use is to enclose the expression in a function that we know the name of and is equal to the identity function.

function constExpression(x) {
   return x;
}

And then in the CSS block we use:

#myDiv {
   border:  10px solid Red;
   width:   expression(constExpression(ieBox ? "100px" : "80px"));
}

Usage

The first thing you need to do is to include the cssexpr.js file. This should be done before any usage of constExpression.

<script type="text/javascript" src="cssexpr.js"></script>

After this you can use constExpression in any style block or any file included using a link element or using the @import directive in a these. Notice that style attributes are not checked due to performance reasons.

Implementation

The idea here is to go through all style sheets and then go through all rules and finally go through all the cssText. To do this we first go through the document.styleSheets collection.

function simplifyCSSExpression() {
   try {
      var ss,sl, rs, rl;
      ss = document.styleSheets;
      sl = ss.length

      for (var i = 0; i < sl; i++) {
         simplifyCSSBlock(ss[i]);
      }
   }
   catch (exc) {
      alert("Got an error while processing css. The page " +
            "should still work but might be a bit slower");
      throw exc;
   }
}

In the style sheet we go thorugh the imports collection and then the rules collection. We also check that the cssText contains "expression(constExpression(" so that we do not process the style sheet in vain.

function simplifyCSSBlock(ss) {
   var rs, rl;

   // Go through imports
   for (var i = 0; i < ss.imports.length; i++)
      simplifyCSSBlock(ss.imports[i]);

   // if no constExpression we don't have to continue
   if (ss.cssText.indexOf("expression(constExpression(") == -1)
      return;

   rs = ss.rules;
   rl = rs.length;
   for (var j = 0; j < rl; j++)
      simplifyCSSRule(rs[j]);
}

Now we go through the cssText of each rule and update the text, using a function named simplifyCSSRuleHelper, until the text does not change any more.

function simplifyCSSRule(r) {
   var str = r.style.cssText;
   var str2 = str;
   var lastStr;

   // update string until the updates does not change the string
   do {
      lastStr = str2;
      str2 = simplifyCSSRuleHelper(lastStr);
   } while (str2 != lastStr)

   if (str2 != str)
      r.style.cssText = str2;
}

The helper function just finds the first expression and evaluates that and replaces the expression with the value.

function simplifyCSSRuleHelper(str) {
   var i, i2;
   i = str.indexOf("expression(constExpression(");
   if (i == -1) return str;
   i2 = str.indexOf("))", i);
   var hd = str.substring(0, i);
   var tl = str.substring(i2 + 2);
   var exp = str.substring(i + 27, i2);
   var val = eval(exp)
   return hd + val + tl;
}

Finally we need to call the function simplifyCSSExpression and this is done once the page is loaded.

if (/msie/i.test(navigator.userAgent) && window.attachEvent != null) {
   window.attachEvent("onload", function () {
      simplifyCSSExpression();
   });
}

CSS Expression Optimization
Demo
Download

Author: Erik Arvidsson