如何判断样式表何时真正加载完

来源:互联网 发布:唱歌 台风 知乎 编辑:程序博客网 时间:2024/06/05 16:54

原文地址:When is a stylesheet really loaded?

require里面css文件加载的插件:https://github.com/guybedford/require-css/blob/master/css.js

我们经常有通过插入一个link节点来加载css文件的需求。通常,我们需要知道文件何时被加载完以进一步做后面的事情,比如执行回调函数等。

长话短说:这个需求的实现真的比它应该有的实现要困难的多,特别是在火狐里面有很多不必要的麻烦。我在此请求代表沮丧的开发人员提出一个请求:火狐4,请在一个样式表加载完毕的时候触发一个load事件。

更新:

如果你认为:“我们坚信这是可以改变的”,请告诉浏览器厂商请按HTML5标准规定,给link元素能触发一个load事件。这里是每个浏览器的bug列表,里面有评论和重现地址:

  • WebKit
  • Firefox
  • Chrome
标准是这么说的:

在试图获取资源的尝试后,如果关键子资源获取到后,如果加载成功了,用户代理必须往队列添加一个任务在在link元素触发一个建单的叫做load的事件。

以上面这种方式,我们看看我们可以有什么。

// my callback function// which relies on CSS being loadedfunction CSSDone() {  alert('zOMG, CSS is done');}   // load me some stylesheetvar url = "http://tools.w3clubs.com/pagr/1.sleep-1.css",    head = document.getElementsByTagName('head')[0];    link = document.createElement('link'); link.type = "text/css";link.rel = "stylesheet"link.href = url; // MAGIC// call CSSDone() when CSS arrives head.appendChild(link);

以下作为奇幻部分的选项,我们从简单且表现很好到荒谬排序:

1、监听link.onload事件;

2、监听link.addEventListener('load');

3、监听link.onreadystatechange;

4、用setTimeout并检查document.styleSheets的变化;

5、用setTimeout并检查你创建的一个元素一个特定元素在有了新的css样式规则后它的样式的变化。

第5条是很疯狂的,假设你有对CSS内容的掌握,忘记它。加上它用setTimeout来检查当前的样式变化,意味着它在刷新回流队列,可能潜在的会变慢。CSS加载完的越慢,回流就越多。因此,不要使用这种方式。

那么,如何实现“魔法”?

// MAGIC  // #1  link.onload = function () {    CSSDone('onload listener');  }  // #2  if (link.addEventListener) {    link.addEventListener('load', function() {      CSSDone("DOM's load event");    }, false);  }  // #3  link.onreadystatechange = function() {    var state = link.readyState;    if (state === 'loaded' || state === 'complete') {      link.onreadystatechange = null;      CSSDone("onreadystatechange");    }  };    // #4  var cssnum = document.styleSheets.length;  var ti = setInterval(function() {    if (document.styleSheets.length > cssnum) {      // needs more work when you load a bunch of CSS files quickly      // e.g. loop from cssnum to the new length, looking      // for the document.styleSheets[n].href === url      // ...            // FF changes the length prematurely :()      CSSDone('listening to styleSheets.length change');      clearInterval(ti);          }  }, 10);    // MAGIC ends

测试

测试页面在这。我们在加载一个在服务端延迟2秒返回的CSS文件。将上面的事件监听器和超时都附加上去。附加另外一个超时,2秒后给出简单的"... and two seconds later ... "的提示。然后观察结果。

测试结果

1、IE会触发readystatechange和onload事件(几年前测试的,现在懒得测了)。现在IE9 addEventListener也支持了吧?

2、火狐(像以前一样)什么也没触发。它会立即更新document.styleSheets的length属性,而不用等css文件被加载完。因此测试中的输出结果是:

zOMG, CSS #1 is done: listening to styleSheets.length change... and two seconds later ...
3、Opera通过onload会触发load以及addEventListener。像火狐一样,他也会增加document.styleSheets.length的值。输出为:

zOMG, CSS #1 is done: listening to styleSheets.length change... and two seconds later ...zOMG, CSS #1 is done: onload listenerzOMG, CSS #1 is done: DOM's load event
4、Chrome和Safari不会触发事件,但是只会在文件被加载完的时候更改document.styleSheets的长度值。

... and two seconds later ...zOMG, CSS #1 is done: listening to styleSheets.length change
总而言之,除了火狐,在各种浏览器中至少有一种方式能告诉我们样式表何时被加载完。这是令人非常尴尬的。

火狐真的没有希望了吗?

如果你真的要疯掉了,魔法5来了----但是他还有很多缺陷。

另外,对象的诀窍应该是能起作用的--似乎所有的浏览器始终都能触发load和/或readystatechange事件。很明显这更加复杂。尽管还没有监控document.styleSheets集合那么复杂。

我也尝试了MozAfterPaint ----他可能也会起作用但对我来说不是,因为我的要加载的css文件并没有改变页面需要重绘的任何样式。很明显没有一个完美的解决方案。

还有一个尝试也失败了:检查document.styleSheets[0].cssRules。尽管document.styleSheets.length被立即更新了,我之前认为火狐在css文件加载完之前不会更新document.styleSheets[n].cssRules(document.styleSheets[n].sheet.cssRules)。然而从火狐3.5(或相近的版本)开始,当文件在不同的域的时候你是没有权限去操作cssRules集合的(CDN说的)。为了安全,你看的出来。甚至是能获取cssRules.length也足够,但是也不行。

其他

1、有没有更聪明的(或者不那么聪明的)关于让我们知道火狐下CSS文件合适加载完的想法?请在评论中指出。
2、库可以从document.styleSheets.length中收益来支持Chrome和Safari。我知道起码YUI3不支持在Safari中(不是火狐)为Y.Get.css()添加回调。
3、火狐4在样式表加载上必须要实现load事件,这是毫无疑问的。IMO所有的浏览器都应该在任何外链资源的时候触发load事件。

更新:火狐的问题解决了!

多谢Ryan,Zach以及 Oleg的评论,证明还是有方法能起作用。这就是:
1、你可以创建一个style元素,而不是link元素;
2、增加:@import "URL"
3、轮询来获取style节点的cssRules集合。
只有在CSS文件被加载完的时候火狐才会去填充这个集合。
var style = document.createElement('style');style.textContent = '@import "' + url + '"'; var fi = setInterval(function() {  try {    style.sheet.cssRules; // <--- MAGIC: only populated when file is loaded    CSSDone('listening to @import-ed cssRules');    clearInterval(fi);  } catch (e){}}, 10);   head.appendChild(style);

我更新了我的测试,发现它工作的很好。
我仍然认为没必要这么复杂,而且所有浏览器就应该简单的触发load事件。对于我来说火狐的这个行为随时会改变,就像Safari不会填充document.styleSheets一样。
有一件事情需要注意,这个对于cssRules的操作的权限获取的失败与另外的域里cssRules操作权限的获取的限制并不是一样的安全检查问题。这种情形下,我们可以获取行内元素style的cssRules而这时很好地。我们仍然不能获取不同域的@import进来的文件的样式。
边注:是我兴奋地是-这项技术也可以预加载js而不会执行js文件(因为没有使用script标签插入到DOM中)。
0 0
原创粉丝点击