In the long history of developer attempts at creating a method of loading CSS Style Sheets via LINK tags with load event support, there has been a lot of FAIL and almost no WINNING! Oddly enough, Internet Explorer has supported a load event on LINK tags for as long as I can remember, but Firefox and the Webkit bunch…not so much.
What are people doing currently to service this need? Well here’s an example of half-baked solutions that have surfaced over the last couple of years on Stack Overflow (I have added my solution to the list, so up-vote it!). Most developer have been reduced to loops that look for the new sheet in the StyleSheet object at short intervals or even loading the link tag in an iframe and using the frame’s onload event, two methods that cause me to puke on my shoes on principle.
The Solution
Well web devs, today is the day. Yup, CSS Style Sheet loading via LINK tags with pretty damn legit load event support is a reality:
var loadCSS = function(url, callback){
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
document.getElementsByTagName('head')[0].appendChild(link);
var img = document.createElement('img');
img.onerror = function(){
if(callback) callback(link);
}
img.src = url;
}
The code above creates a LINK tag for your CSS file and inject it into the head of the document, there's certainly nothing odd about that. Next comes the interesting part, it creates an IMG tag and adds your CSS file location as src parameter of the element then injects it into the page. The browser parser downloads the file and attempts to parse it, which predictably fails as it is the wrong MIME type. That failure triggers the error event on the image element. We know the file is present at that point, so the load event you pass to the function is fired from inside the image element error event, the image element is then deleted from the document. Whether cached or not, this method will fire your load event for any CSS file you include, when the file is in the cache it is called immediately - which is a huge benefit.
Try it out!
Here it is a live demo, I have added an alert as the load event default for example sake:
this is very cool. It would be great if a fully cross-browser solution could be found, though. This will work on FF and Chrome(-ium), and the link onload event can be used in IE and Opera, but what about Safari, for example?
Should work in Safari too, as that is also a Webkit-based browser.
I have limited access to Mac systems to test on, but from what testing I’ve been able to do, it seems to work on Safari 5.x, but not 4.x. Just FYI!
I’ve implemented this solution in a project I’m working on, and it fails in Safari 4.x and 5.0.2 on mac. Might have to write an ajax version instead…
I had updated this script quite some time ago to remove the part that appends the img element to the document (the source of the Safari issues), but I forgot to update the jsFiddle example on this post 🙁
Can you check if it is working now and let me know?
When the css file isn’t found,it also can trigger the img element’s onerror event.How can you be sure that the css file can be loaded successfully?
You can also use XHR to retrieve the contents of the CSS file, then inject a style tag in the head rather than a link tag.
This of course only works if you are requesting CSS from your own domain, but yes that would work in a fair number of cases.
Could this potentially change the ‘cascade’ order of stylesheets, where embedded stylesheets are given more importance than external stylesheets?
Dude good work, i just want to add img.style.display=”none”;
Hi,
Here is my optimized version:
var img = document.createElement(‘img’);
img.onerror = function() {
load(link);
}
img.src = link.href;
There is no need to append the image to the body and remove it. Just assing ‘onerror’ callback before assigning ‘src’ attribute.
martin-g
Firefox 9 will fire onload: https://bugzilla.mozilla.org/show_bug.cgi?id=185236
although it works, it isn’t pretty. funny to see ie does something right this time.
1. Whether or not the file is “cached” is not really relevant most times, you also want to know when the styles have applied.
2. The onerror will also fire on 404, so it’s not really reliable.
You might be able to see if the corresponding sheet object is undefined, I’ll investigate.
This will fail in some browsers. Eric Law has a very thorough description of this situation: http://blogs.msdn.com/b/ieinternals/archive/2012/05/05/problems-with-using-img-to-prefetch-script-or-css.aspx
That’s just fine, seeing as all the browser’s he highlighted in his post already supported a real LINK onload event (IE has had that event since 5.5). The recommendation in the case your browser already supports LINK onload: use that instead 😉
Is it possible to use events, wich are povided by css3 animation styles? Like “animationstart” for example.
https://developer.mozilla.org/en/CSS/CSS_animations
@Peter Müller That’s only in IE, where link tag onload was always supported anyway – the other browsers, the targets of this method, do not handle resources in the same way.
@Michail That is a great alternative technique, obviously it is a bit more intrusive, as it requires actual code to be in each of the stylesheets you request – but still a valid alternative!
@David”Not reliable” is quite relative. Most people don’t have an onerror. fail case for stylesheets anyway, probably because it’s pretty hard to gracefully degrade the lack of a stylesheet. The error event will fire for 404s too, but to make sure the error is the ‘correct’ type, you can simply check: myLinkElement.sheet.cssRules.length > 0; If it isn’t, your sheet isn’t there.
Here’s the relevant part from the article you linked that shows this isn’t a problem for the browsers this method is intended for: “The same abort behavior exists in IE6 to IE10, and Firefox 12.0. Opera 11.61, Chrome 18, and Safari 5.1.5 do not appear to abort when invalid image content is downloaded.” – I don’t believe the author is correct about Firefox. In my testing, Firefox only aborts after the file is downloaded and the parser fails due to the incorrect MIME type.
@ryan beard I tweaked the method a bit, I believe this was due to the fact I was first injecting the IMG into the HEAD of the document, which Safari didn’t like. It should work now, let me know!
@Michele Carino I thought that was strange too, but looking at the code adding the styles is unnecessary as the image is never actually attached to the DOM, instead it only exists in memory.
Thanks so much for this, because of this, my dominject project – http://github.com/bevry/dominject – now works for Android when injecting stylesheets!
The onerror. handler gets called as soon as a reponse is received from the server, it happens some time before the file is fully received. That time is the receiving time and depends on the file size.
An alternative version using Q/promises and providing a quick short circuit if the tag already exists and an id is provided.
nyteschayde nice, I will try to add something to the post with this update!
Promises aren’t for everyone but the don’t repeat if it already exists is useful.
Just adding my two cents.
TL;DR
Sometimes onerror. will not fire (even as it should). In my case some CSS files fired onload event on img object.
More details:
I am working on a webapp that dynamically load JS and CSS assets. Since there is quite a lot of different components (each has it’s own JS and CSS) on the page there is also a lot of requests for CSS files and I need to know when something has loaded.
Your workaround for onload event on link elements is great but while testing with stock browser on Android 4.3 (Samsung Galaxy S III) I noticed that sometimes (and in my case always for the same CSS file) onerror. event is not fired.
On a hunch I also added onload event (which presumably should never fire) to the img object and guess what… That specific file fired onload event instead of onerror..
@Kristopher Giesing This idea seems like the most robust, given the fact that the image node’s `onError` throws for 404s as well. But you don’t need to inject the CSS into a `style` element; this technique works just as well with a `link` element as the `onError` technique does.
(With both techniques, gotta make sure the server is sending the right sort of cache headers, so you don’t put an extra download burden on the user.)
@Kristopher Giesing that’s only if everything you load is from your base domain, which would exclude things like CDNs and other useful content services.
@Michele Carino you don’t need that because the image element is never injected into the DOM.