@madpilot makes

Use CSS to speed your unobtrusive JavaScript

Unobtrusive JavaScript does for JavaScript what CSS did for HTML design. Separating the design and business logic client side means better re-use and compatibility.

To make a truly unobtrusive site, you should start with a base, non-JavaScript version of the site, and then add functionality dynamically. One of the issues with this is quite often you are left with elements that are required for HTML-only functions, but not for the JS version. Obviously, it’s easy to traverse the DOM tree and hide elements that not required like this:

var elementToHide = document.getElementById('hide_me');

if(elementToHide) {
    elementToHide.style.display = 'none';
}

but this can be pretty slow if you have lot of elements to hide. Sure, you could use XPath, but it isn’t supported in many browsers.

CSS is pretty darn quick when applying styles (well, you would hope so – that’s what it is designed for), so by dynamically including a CSS file via JavaScript we can hide (and show) elements really quickly. It’s all pretty simple:

  1. Create the style sheet (We’ll call it /stylesheets/css.js)
  2. Set up the desired styles in the style sheet as you would any another CSS file
  3. Drop the following code is to a JavaScript file and include it in your header
function unobtrusiveCSS() {
    head = document.getElementsByTagName('head');
    head = head[0];
    link = document.createElement('link')
    link.href = '/stylesheets/js.css';
    link.setAttribute('media', 'screen')
    link.setAttribute('rel', 'stylesheet');
    link.setAttribute('type', 'text/css');
    head.appendChild(link);
  }

  FastInit.addOnLoad(unobtrusiveCSS);

Now, you might have noticed that I’m using the FastInit library (written by the ever so talented Andrew Tetlaw). Because we are modifying the DOM, we need to wait for it to load before we can append anything. I could have used window.onload, but that would defeat the purpose, as we would have to wait for all of the images on the page to load, negating the speed up we would get.

The FastInit library will fire the JavaScript as soon as the DOM is loaded. (Go to the site to find out how it works – it’s quite ingenious), At the end of the day, you would need to a library like this regardless of whether you use the CSS include hack.

Caveat: Another way of doing it would be to use document.write, which would eliminate the need for an external library, but hacks like that make baby jeebus cry.

8 comments

  1. Yeah I can contest that is really does work a lot faster than a bunch of Javascript CSS inserts on the DOM. great of just generally changing a segment of the page to a different style or even visual production.



    With CSS3 (gotta look forward) You can even cheat and preloading the JS Stylesheet. Use a media query, something like media="screen and (max-width:1px)" hence the style is only applied to screens of 1px. You just fire a change to the media attribute. Okay it only works on browsers that support CSS3 in part. But it's nice to see what's possible. Myles do you know of an alternative way to fast load the js.css
  2. Interesting method. You would need to balance the complexity of your DOM and the number of elements you are styling with the cost of a round trip to the server.



    While we are looking forward, I'd be interested in seeing how this stacks up with Safaris squirrelfish js engine which "elegantly eliminates almost all of the overhead of a tree-walking interpreter"
  3. Gary: Having a quick read through the CSS2 spec, it would seem that the media attribute is not fuly defined:



    "CSS2 does not specify a definitive list of media types that may be values for @media"



    So I'm wondering if you could load it up using a fake media attribute and then dynamically change it using JS.



    I'll have a play and see if that is legal.
  4. Terrence: Once all of the browsers implement a faster DOM walker, techniques like this won't be needed.



    They also all need to implement an onDOMLoad event (as firefox does), which would make the FastInit library obsolete. Currently it uses a CSS hack in IE and timers in Safari to work.
  5. Humm fake media attributes fall back to all or screen in IE 5 and 6 - grrrr
  6. Interesting approach, Myles. Rather than load an external CSS, I just scope my JS-specific styles in an existing file, by adding a class of 'js' to the HTML element on DomReady.



    Like so:



    html.js #slideshow img {/* some rules that only pertain when JS is enabled */}



    I think it's probably faster than your technique, but not having thought of yours, I'll have to test sometime.



    My favourite part of this methodology (both your and mine) is that you can choose to support various capabilities rather than a simple binary query of whether Javascript is enabled.
  7. Lachlan: That's a cool way of achieving the same end. I'd be interesting to see which is quicker - although I must admit, your solution looks better. Thanks for pointing it out!
  8. No worries, Myles! I came up with it a while back for a pretty JS-heavy app I was working on. Worked a treat.

Leave a comment