Reputation: 11079
I have some code (it's actually not mine but the SlickGrid library) that creates a <style>
element, inserts it into the DOM, then immediately tries to find the new stylesheet in the document.styleSheets collection.
In WebKit this sometimes fails. I don't actually have any idea what the circumstances are, but it's nothing that's consistently reproducible. I figured I could work around it by changing the code so the check for the StyleSheet object doesn't happen until the load
event on the style element, like so:
$style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
var rules = ...;// code to create the text of the rules here
if ($style[0].styleSheet) { // IE
$style[0].styleSheet.cssText = rules.join(" ");
} else {
$style[0].appendChild(document.createTextNode(rules.join(" ")));
}
$style.bind('load', function() {
functionThatExpectsTheStylesheet();
});
and functionThatExpectsTheStylesheet attempts to locate the actual stylesheet object like so:
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
stylesheet = sheets[i];
break;
}
}
but sometimes even at that point, the stylesheet object is not found.
So, my question is this:
load
event in fact not guarantee that the styleSheet object will be available? Is it a bug?Upvotes: 8
Views: 1634
Reputation: 15356
Had same need, using detection for style's rule to check if stylesheet has been loaded.
// Load CSS dynamically. There's no way to determine when stylesheet has been loaded
// so we usingn hack - define `#my-css-loaded {position: absolute;}` rule in stylesheet
// and the `callback` will be called when it's loaded.
var loadCss = function(url, cssFileId, callback){
// CSS in IE can be added only with `createStyleSheet`.
if(document.createStyleSheet) document.createStyleSheet(url)
else $('<link rel="stylesheet" type="text/css" href="' + url + '" />').appendTo('head')
// There's no API to notify when styles will be loaded, using hack to
// determine if it's loaded or not.
var $testEl = $('<div id="' + cssFileId + '" style="display: none;"></div>').appendTo('body')
var checkIfStyleHasBeenLoaded = function(){
if($testEl.css('position') == 'absolute'){
$testEl.remove()
callback()
}else setTimeout(checkIfStyleHasBeenLoaded, 10)
}
setTimeout(checkIfStyleHasBeenLoaded, 0)
}
Upvotes: 0
Reputation: 2141
Dynamically loading CSS stylesheets is still an area filled with browser quirks, unfortunately. In Webkit, <style>
and <link>
elements will both fire load
and error
events when loading stylesheets. However, the load
event itself means only that the stylesheet resource has been loaded, not necessarily that it has been added to document.styleSheets
.
The require-css RequireJS loader deals with this issue by branching its loading mechanism based on userAgent sniffing (it is nearly impossible to feature-detect whether or not the <link>
tag will fire its load
event properly). Specifically for Webkit, the detection resorts to using setTimeout
to find when the StyleSheet object has been attached to document.styleSheets
var webkitLoadCheck = function(link, callback) {
setTimeout(function() {
for (var i = 0; i < document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href == link.href)
return callback();
}
webkitLoadCheck(link, callback);
}, 10);
}
So while the load
event will fire on Webkit, it is unreliable for the purposes of being able to access the corresponding StyleSheet
instance.
Currently the only engine that supports stylesheet load
events properly is Firefox 18+.
Disclosure: I am a contributor to require-css
References:
Upvotes: 6
Reputation: 10698
I don't claim to have a solution, neither an explanation of the Webkit loading timing, but I recently had a similar problem, but with JS : I needed to be absolutely sure that the jQuery framework was loaded before I include other libraries of my own, which depended on jQuery.
I then noticed that the load
event of jQuery wasn't fired when I expected it.
For what it's worth, here how I solved my problem :
var addedHead = window.document.createElement('link');
addedHead.async = true;
addedHead.onload = addedHead.onreadystatechange = function () {
if (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')
{
callback();
addedHead.onload = addedHead.onreadystatechange = null;
}
};
//And then append it to the head tag
You could give it a try!
Upvotes: 0
Reputation: 1594
I think the load event could be triggered earlier than the css stylesheet. The issue would happen maybe 1 of 10 times, but its still not best practice and the most clean way.
setTimeout is surely a good approach, as it can be guaranteed that your script loads the css first. However, since only the tag of the stylesheet, means, the link to it is created, the css need to be downloaded first, so whatever approach of these two you use it is never 100% guaranteed that your css is loaded before the load event fires.
To be 100% sure that everything runs in correct order is, to make a synchronous fileread via AJAX, or an asynchronous AJAX call with a helper function which checks if the CSS is ready (this way you dont freeze the Browser but still have the ability to check wheter the file is loaded or not). Another way would be to just link the stylesheets manual: You set the stylesheets first and then comes the code in the header of your HTML file.
I didnt look so deep into the load events, but it could be that the DOMContentLoaded event is only firing as CSS is loaded (ill research on this one)
Look here for similiar question: jQuery event that triggers after CSS is loaded?
Upvotes: 0