Zach’s ugly mug (his face) Zach Leatherman

Faking Onload for Link Elements

July 29, 2010

Updated 2011/09/27: Rejoice! This issue has now been fixed in Firefox.


Or, I Am Dynamically Loaded CSS (and So Can You!)

Dynamic resource loading is one of the keys to have a performance happy web application. There are generally three different criteria we must address when making a request: cross domain security policies, asynchronous/synchronous (will it block the host page while loading), and whether or not events are triggered when the request completes.

If the resource and host page are on the same domain, obviously XMLHttpRequest works the best. We can control whether or not the resource is loaded asynchronously or synchronously, and we know exactly when it gets done.

If the resource and host page are on different domains (increasingly more common with CDN’s), our options narrow. Loading the JavaScript is a solved problem, just use the onload event on the `` tag and you’re good to go (onreadystatechange for IE). But CSS is more complicated.

Resource Method Option for (a)synchronous Event
JavaScript/CSS Same Domain XMLHttpRequest Both onreadystatechange
JavaScript Different Domain <script> Synchronous (Asynchronous where async property is supported) onload
onreadystatechange for IE
CSS Different Domain <link> Asynchronous What this blog post is about.

Existing Solutions

In all of the library source code I evaluated, Internet Explorer didn’t cause any issues. It fires both the onload and onreadystatechange events for `` nodes. Obviously this is ideal behavior, and IE got it right. But what about Firefox and Safari/Chrome?

YUI 2.8.1 and 3.1.1

Original Source

// FireFox does not support the onload event for link nodes, so there is
// no way to make the css requests synchronous. This means that the css
// rules in multiple files could be applied out of order in this browser
// if a later request returns before an earlier one.  Safari too.
if ((ua.webkit || ua.gecko) && q.type === "css") {
    _next(id, url);
}

I wouldn’t be surprised if the commit log there was from Bon Jovi; that code is living on a prayer.

LazyLoad

Original Source

// Gecko and WebKit don't support the onload event on link nodes. In
// WebKit, we can poll for changes to document.styleSheets to figure out
// when stylesheets have loaded, but in Gecko we just have to finish
// after a brief delay and hope for the best.
if (ua.webkit) {
    // resolve relative URLs (or polling won't work)
    p.urls[i] = node.href;
    poll();
} else {
    setTimeout(_finish, 50 * len);
}

Better, closer, warmer. This includes a nice method for working with webkit browsers. The poll method compares document.styleSheets, since Webkit has the nice option of only appending to the styleSheets object when the styleSheet has successfully loaded.

So we have working solutions for IE and Safari/Chrome. The only unsolved piece of the puzzle here is Firefox.

This post from the same author as LazyLoad also describes another solution which involves modifying the source CSS and polling against it. But that’s not really ideal. Can we do better?

Solution

Here’s what I came up with (using jQuery for brevity, note that this solution only fixes Firefox, and does not incorporate the above already solved solutions):

var url = 'css.php',
    id = 'dynamicCss' + (new Date).getTime();

$('<style/>').attr({
    id: id,
    type: 'text/css'
}).html('@import url(' + url + ')').appendTo(document.getElementsByTagName('head')[0]);

function poll() {
    try {
        var sheets = document.styleSheets;
        for(var j=, k=sheets.length; j<k; j++) {
            if(sheets[j].ownerNode.id == id) {
                sheets[j].cssRules;
            }
        }
        // If you made it here, success!
        alert('success!');
    } catch(e) {
        window.setTimeout(poll, 50);
    }
}

window.setTimeout(poll, 50);

See this Demo in Action (Firefox Only)

Update: After much joy and celebration, I have discovered that an approach similar to the above was written by Oleg Slobodskoi in his xLazyLoader plugin for jQuery. It shouldn’t be surprising that two independent developers might reach the same solution, and is just more proof that software patents are stupid. :)

Update #2 Added note about HTML5 async property on script tags.


< Newer
The JavaScript Testing Challenge
Older >
ALARMd is now on Github

Zach Leatherman IndieWeb Avatar for https://zachleat.com/is a builder for the web at IndieWeb Avatar for https://fontawesome.com/Font Awesome and the creator/maintainer of IndieWeb Avatar for https://www.11ty.devEleventy (11ty), an award-winning open source site generator. At one point he became entirely too fixated on web fonts. He has given 84 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »

3 Comments
  1. bga Disqus

    30 Jul 2010
    Firefox and Webkit css load with onload and onerror detection http://yuilibrary.com/proje...
  2. Adam Disqus

    30 Jul 2010
    So "sheets[j].cssRules" throws an error if it's not there? Could you also do "if (cssRules in sheets[j]) {...}"? (I prefer not to use errors to control flow.)Also, have you tried the shorter "+new Date" to "(new Date).getTime()"?Otherwise, great find! Thanks!
  3. Daniel Buchner Disqus

    10 Apr 2011
    No need to fake it anymore, just trick browsers that don't support onload for CSS into giving you one: Event-based method for CSS onload
Shamelessly plug your related post

These are webmentions via the IndieWeb and webmention.io.

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)