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

原文地址: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列表,里面有评论和重现地址:

标准是这么说的:

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

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

// my callback function
// which relies on CSS being loaded
function CSSDone() {
  alert('zOMG, CSS is done');
}
  
 
// load me some stylesheet
var 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 listener
zOMG, 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事件。

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

多谢RyanZach以及 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值