May 27

stylesheet.onload: or lack thereof

For a version of stylesheet.onload that works cross domain, please see: When is a stylesheet really loaded?by @stoyanstefanov

Loading stylesheets into your page dynamically can be a good way of reducing your initial page download size. You may want to load a print stylesheet in only when a user initiates a print action, you may want to allow your users to change the theme or skin of your site/ app or before displaying some content loaded in dynamically via AJAX.

There’s only one caveat, you need to know that the stylesheet has actually loaded before you start the print action, or display your content to avoid any mishaps or foucs. This is easy when using AJAX or inserting a script or image node into the page. Unfortunately though, most browsers have not implemented the same functionality for the humble link node.

Funnily enough, Internet Explorer supports the onreadystatechange event and Opera supports the onload event for link nodes. This still leaves us with a large gap of unsupported browsers and the IE and Opera only tell us that the stylesheet has completed loading and not whether it was loaded successfully. So, what are our options then?

  1. Don’t support this kind of functionality
  2. Use AJAX to load in the stylesheet’s content, create a style node and insert the XHR responseText into the style node.
  3. Dynamically create a link node, load the stylesheet in and use setTimeout to run a callback after a abitrary amount of time has elapsed (e.g. 250ms), and hope it all works out.

None of these options really seems viable, although option 2 is probably the nicest of the 3. There is one more option though: option 4. Oooohhhh…

Option 4: The bestest option so far.

Even though there is no event to listen to, we do have a way we can check a stylesheet has loaded in the remaining browsers: by checking it’s cssRules length.

In W3C compliant browsers the link node will have a sheet property which, in itself, contains a cssRules property, which, as you can probably guess, is a list of all the CSS rules in the style sheet. If the style sheet is loaded, and not empty, then:

link_node.sheet.cssRules.length > 0;

As you can expect, Internet Explorer, has a slightly different syntax:

link_node.styleSheet.rules.length > 0;

So using this little piece of knowledge and a little setInterval/ setTimeout magic we can create a script that allows us to load style sheet into the page dynamically and then fire a callback when it has completed.

With option 4, we can create a function that loads a style sheet in any browser and fires a callback when it is has loaded. The callback will tell us if it was successfull or unsuccessfull (it would only be unsuccessful if the file did not exist or the style sheet had no rules in it).

The basics

The most basic implementation of this can be done in a mesely 30 lines of — framework independent — JavaScript code:

function loadStyleSheet( path, fn, scope ) {
   var head = document.getElementsByTagName( 'head' )[0], // reference to document.head for appending/ removing link nodes
       link = document.createElement( 'link' );           // create the link node
   link.setAttribute( 'href', path );
   link.setAttribute( 'rel', 'stylesheet' );
   link.setAttribute( 'type', 'text/css' );

   var sheet, cssRules;
// get the correct properties to check for depending on the browser
   if ( 'sheet' in link ) {
      sheet = 'sheet'; cssRules = 'cssRules';
   }
   else {
      sheet = 'styleSheet'; cssRules = 'rules';
   }

   var interval_id = setInterval( function() {                    // start checking whether the style sheet has successfully loaded
          try {
             if ( link[sheet] && link[sheet][cssRules].length ) { // SUCCESS! our style sheet has loaded
                clearInterval( interval_id );                     // clear the counters
                clearTimeout( timeout_id );
                fn.call( scope || window, true, link );           // fire the callback with success == true
             }
          } catch( e ) {} finally {}
       }, 10 ),                                                   // how often to check if the stylesheet is loaded
       timeout_id = setTimeout( function() {       // start counting down till fail
          clearInterval( interval_id );            // clear the counters
          clearTimeout( timeout_id );
          head.removeChild( link );                // since the style sheet didn't load, remove the link node from the DOM
          fn.call( scope || window, false, link ); // fire the callback with success == false
       }, 15000 );                                 // how long to wait before failing

   head.appendChild( link );  // insert the link node into the DOM and start loading the style sheet

   return link; // return the link node;
}

This would allow you to load a style sheet with an onload callback function like this:

loadStyleSheet( '/path/to/my/stylesheet.css', function( success, link ) {
   if ( success ) {
      // code to execute if the style sheet was loaded successfully
   }
   else {
      // code to execute if the style sheet failed to successfully
   }
} );

Or if you want to your callback to maintain its scope/ context, you could do something kind of like this:

loadStyleSheet( '/path/to/my/stylesheet.css', this.onComplete, this );

If you want to see it in action I’ve created a test page. I’ve also created a nicer version of loadStyleSheet which:

  • Allows you to add multiple callbacks per stylesheet
  • Uses onreadystatechange and onload events for browsers that support them; and
  • Does not try and load the same stylesheet if it is still in the DOM — if you try and load the same style sheet twice it will simply fire the callback you passed, with success == true.

It has it’s own test page test page. loadStyleSheet.js is about 1kb gzipped.


  1. borislit reblogged this from thudjs and added:
    Since its impossible...load/onload listener on CSS files, this blog post give
  2. thudjs posted this
Page 1 of 1