原文地址: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 event4、Chrome和Safari不会触发事件,但是只会在文件被加载完的时候更改document.styleSheets的长度值。
... and two seconds later ... zOMG, CSS #1 is done: listening to styleSheets.length change总而言之,除了火狐,在各种浏览器中至少有一种方式能告诉我们样式表何时被加载完。这是令人非常尴尬的。
火狐真的没有希望了吗?
其他
更新:火狐的问题解决了!
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);
我更新了我的测试,发现它工作的很好。