1. 程式人生 > >javascript建立css、js,onload觸發callback相容主流瀏覽器的實現

javascript建立css、js,onload觸發callback相容主流瀏覽器的實現

由於需要寫個函式,既可以載入css,又可以載入js,所以對各主流瀏覽器對載入js、css後是否觸發onload事件做了個測試。當然,為了相容,首先要考慮的是會用到onload和onreadystatechange,但他們並不是萬能的。載入js檔案onload觸發callback不算大問題。css比較特殊,因為Webkeit/FF下載入css不會觸發onload事件。所以研究了一晚上才找到個相容的辦法,分享如下:

首先測試元素直接寫在頁面

<link onload="alert('css onload')" rel="stylesheet" href="http://localhost/css/123.css"/>
<script onload="alert('js onload')" src="http://localhost/123.js"></script>
<link onreadystatechange="alert('css readystatechange')" rel="stylesheet" href="http://localhost/css/123.css"/>
<script onreadystatechange="alert('js readystatechange')" src="http://localhost/123.js"></script>

CSS onload: 支援: IE6-9/OP, 不支援: FF/Webkit(SF/CM)
JS onload: 支援: IE9/OP/FF/Webkit, 不支援: IE6-8

CSS onreadystatechange: 支援: IE6-9, 不支援: OP/FF/Webkit
JS onreadystatechange: 支援: IE6-9, 不支援: OP/FF/Webkit

測試js建立元素

var doc = document,
    head = doc.getElementsByTagName("head")[0],
    css, js;

css = doc.createElement('link');
css.href = 'http://localhost/123.css?2';
css.rel = 'stylesheet';
head.appendChild(css);

js = doc.createElement('script');
js.src = 'http://localhost/123.js?2';
head.appendChild(js);

css.onload = function(){
    alert('css onload')
}
js.onload = function(){
    alert('js onload')
}
css.onreadystatechange = function(){
    alert('css readystatechange')
    //alert(this.readyState) //IE可以得到loading/complete, OP為undefined
}
js.onreadystatechange = function(){
    alert('js readystatechange')
    //alert(this.readyState) //IE可以得到loading/loaded, OP為loaded
}

CSS/JS onload:(同元素直接寫在頁面是一樣的)

CSS onreadystatechange: 支援: IE6-9/OP, 不支援: FF/Webkit (這裡有區別,OP支援js建立的css元素,但readyState為undefined)
JS onreadystatechange: 支援: IE6-9/OP, 不支援: FF/Webkit (這裡有區別,OP支援js建立的js元素,readyState為loaded)

所以為了更大的相容,onload、onreadystatechange都要寫上,程式碼類似下面:

// 先把js或者css加到頁面: head.appendChild(node);
// onload為IE6-9/OP下建立CSS的時候,或IE9/OP/FF/Webkit下建立JS的時候
// onreadystatechange為IE6-9/OP下建立CSS或JS的時候
node.onload = node.onreadystatechange = function(){
	// !this.readyState 為不支援onreadystatechange的情況,或者OP下建立CSS的情況
	// this.readyState === "loaded" 為IE/OP下建立JS的時候
	// this.readyState === "complete" 為IE下建立CSS的時候
	if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") {
		node.onload = node.onreadystatechange = null; //防止IE記憶體洩漏
		alert('loaded, run callback');
	}
}

jquery、kissy的原始碼,判斷載入成功的核心部分差不多都這樣實現的。

存在的問題:
1、當瀏覽器同時支援onload、onreadystatechange的情況會觸發上面的函式兩次,
比如:
IE9載入JS的時候,會alert兩次,載入CSS的時候,alert一次,註釋掉“onload、readystatechange=null”,alert兩次。
OP載入JS/CSS,alert一次,把“onload、readystatechange=null”註釋也會alert兩次。
解決:
先在外部設定個變數isLoaded = false;
"if (!this.readyState..."上面加上個判斷,如果已經載入成功就返回,比如:if (isLoaded) { return; }
"node.onload =..."上面加上 isLoaded = ture;
JQ有沒有加這個我忘記了、KS應該是加了類似的判斷了。

2、這個方法載入JS呼叫callback是相容性沒問題了,但是載入CSS再callback支援情況不同了:
IE6-9/OP可以成功alert,但是FF/Webkit不支援css的onload,解決辦法:

2.1、讀取cssRules的length來判斷是否載入成功,缺點不能跨域讀取(非IE)。

var doc = document,
head = document.getElementsByTagName( 'head' )[0],
link = doc.createElement('link');
	//link.href = 'http://xxx.com/123.css'; //跨域
	link.href = 'http://localhost/123.css';
	link.rel = 'stylesheet';
	head.appendChild( link );

var sheet, cssRules;
if ( 'sheet' in link ) { //FF/CM/OP
	sheet = 'sheet'; cssRules = 'cssRules';
}
else { //IE
	sheet = 'styleSheet'; cssRules = 'rules';
}

var _timer1 = setInterval( function() { // 通過定時器檢測css是否載入成功
	try {
		if ( link[sheet] && link[sheet][cssRules].length ) { // css被成功載入
			// console.log(link[sheet][cssRules]);
			clearInterval( _timer1 ); // 清除定時器
			clearTimeout( _timer2 );
			alert('loaded, run callback'); // 載入成功執行callback
		}
	} catch( e ) {
		// FF看到的可能的報錯:
		//本地:nsresult: "0x8053000f (NS_ERROR_DOM_INVALID_ACCESS_ERR)" ,因為沒載入完成還不能讀取,載入完畢就不會報錯了
		//跨域:Security error, code: "1000" nsresult: "0x805303e8",因為不能跨域讀取CSS。。。
		//關於跨域訪問:FF/OP/CM都禁止,IE6-9都可以跨域讀取css。
	} finally {}
}, 20 ),
// 建立超時定時器,如果過10秒沒檢測到載入成功
_timer2 = setTimeout( function() {
	clearInterval( _timer1 ); // 清除定時器
	clearTimeout( _timer2 );
	alert('loaded ? run callback'); // 都過了這麼長時間了,雖然沒判斷載入成功也執行callback(這裡可能本身就載入失敗,也可能是跨域的情況)
}, 10000 );
//@See: http://stackoverflow.com/questions/5537622/dynamically-loading-css-file-using-javascript-with-callback-without-jquery

2.2、建立div#hack4loaded,並新增到頁面,然後定時讀取div的樣式,如果樣式是css裡面設定的就說明載入成功,此方法不存在跨域問題,所有瀏覽器都OK。(css裡面加上一個樣式: #hack4loaded{display:none})

var doc = document,
head = doc.getElementsByTagName('head')[0],
link = doc.createElement('link');
	link.href = 'http://localhost/123.css';
	link.rel = 'stylesheet';
	head.appendChild( link );

var div  = document.createElement('div');
    div.id = 'hack4loaded';
    var getStyle = function(name){ //獲取css裡面設定的元素樣式
		if(div.currentStyle){ // IE/OP
			return div.currentStyle[name];
		}else{ // FF/CM
			return div.ownerDocument.defaultView.getComputedStyle(div, null)[name];
		}
    };
document.body.appendChild(div); // 新增到頁面
//建立定時器,讀取建立的div的樣式是否是已經在css裡面設定好的
var _timer1= setInterval( function() {
	//console.log(getStyle('display'));
	if ('none' === getStyle('display')) { //樣式和css裡面設定的一樣,說明載入成功
		clearInterval( _timer1 ); // 清除定時器
		clearTimeout( _timer2 );
		div.parentNode.removeChild(div); //移除div
		alert('loaded, run callback'); // 載入成功執行callback
	}
}, 50 ),
// 建立超時定時器,防止css檔案載入失敗的情況
_timer2 = setTimeout( function() {
	clearInterval( _timer1 ); // 清除定時器
	clearTimeout( _timer2 );
	alert('fail to load'); // 這個沒載入成功的可能性很大,再就是網速太慢超時了
}, 10000 );

所以總結如下:

javascript建立js或者css,可以相容瀏覽器在onload時觸發callback。
只要把上面的“node.onload = node.onreadystatechange = function()”方法,再結合2.2的建立div#hack4loaded的方法結合再一起,就可以建立一個比較完美的支援載入js、css,並可以在檔案載入完畢觸發callback的函式。

由於IE/OP支援css的onload,所以寫這個方法的時候可以考慮非IE/OP才用2.2的方法判斷css載入完成。