最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Webkit - dynamically created stylesheet - when does it really load? - Stack Overflow

programmeradmin0浏览0评论

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:

  1. Does the load event in fact not guarantee that the styleSheet object will be available? Is it a bug?
  2. If not, is there another condition that I can use or do I just need to do this using timeouts?
  3. Or is there some problem with the way I'm binding the load event and that's my problem?

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:

  1. Does the load event in fact not guarantee that the styleSheet object will be available? Is it a bug?
  2. If not, is there another condition that I can use or do I just need to do this using timeouts?
  3. Or is there some problem with the way I'm binding the load event and that's my problem?
Share Improve this question edited Jul 22, 2013 at 14:23 Dan asked Jul 19, 2013 at 13:42 DanDan 11.1k7 gold badges53 silver badges82 bronze badges 5
  • 1 I think making a Fiddle would be helpful – Brewal Commented Jul 22, 2013 at 14:34
  • 1 May be a duplicate of stackoverflow./questions/8060429/… – Dima Kuzmich Commented Jul 22, 2013 at 15:07
  • @Brewal I can try that but it happens less than 10% of the time in my real test cases, so I don't know how helpful that would be. – Dan Commented Jul 22, 2013 at 17:36
  • @DimaKuzmich it does appear to be a very similar issue to the linked question. – Dan Commented Jul 22, 2013 at 18:23
  • Ok, but I think that the main idea of the issue is same. – Dima Kuzmich Commented Jul 22, 2013 at 19:01
Add a ment  | 

4 Answers 4

Reset to default 6 +200

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:

  • http://pieisgood/test/script-link-events/
  • https://bugs.webkit/show_bug.cgi?id=56436
  • https://bugs.webkit/show_bug.cgi?id=38995
  • https://developer.mozilla/en-US/docs/Web/HTML/Element/link
  1. 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.

  2. 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 es 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?

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 == 'plete') 
    {   
        callback();
        addedHead.onload = addedHead.onreadystatechange = null;
    }
};
//And then append it to the head tag

You could give it a try!

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)
}
发布评论

评论列表(0)

  1. 暂无评论