由于需要写个函数,既可以加载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、chrome中可用,FF中不可用)。

FF下跨域加载css读取cssRules.length时会报:
// [Exception... "Security error"  code: "1000" nsresult: "0x805303e8 (NS_ERROR_DOM_SECURITY_ERR)"
	var 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 head = document.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加载完成。

整理后的代码:

// 加载css文件
        loadCss : function(file){
            var head = document.getElementsByTagName('head')[0];
            link = document.createElement('link');
            link.href = file;
            link.rel = 'stylesheet';
            head.appendChild( link );

            return link;
        },

        // 加载js文件
        loadJs : function(file){
            var head = document.getElementsByTagName('head')[0];
            var js = document.createElement('script');
            js.src = file;
            head.appendChild(js);

            return js;
        },

	// 加载css和js
	loadFile : function(callback, callbackFailed){
	    var cssElements = [];
	    var jsElements = [];

	    // 加载css
		for(var k in this.config.css){
			var file = this.config.css[k];
			cssElements[cssElements.length] = this.loadCss(file);
		}

		// 加载js
		for(var k in this.config.js){
			var file = this.config.js[k];
			jsElements[jsElements.length] = this.loadJs(file);
		}

		var sheet, cssRules;
	    if ($.browser.msie) { //IE
		sheet = 'styleSheet'; cssRules = 'rules';
	    } else { //FF/CM/OP
		sheet = 'sheet'; cssRules = 'cssRules';
	    }

		// 检测css是否加载完成,加载完成后执行callback
		var checkCssLoad = function(){
		    var _timer1 = setInterval( function() { // 通过定时器检测css是否加载成功
	    try {
		var loadComplete = true;
		for(var i=0; i<cssElements.length; i++){
		    var node = cssElements[i];
		    if ( !node[sheet] || !node[sheet][cssRules].length ) {
			loadComplete = false;
			break;
		    }
		}
		// js加载完成
		if (loadComplete){
		    clearInterval( _timer1 ); // 清除定时器
		    clearTimeout( _timer2 );
		    // 检测css的加载情况
		    callback();
		}
	    } catch( e ) {
		// FF看到的可能的报错:
		//本地:nsresult: "0x8053000f (NS_ERROR_DOM_INVALID_ACCESS_ERR)" ,因为没加载完成还不能读取,加载完毕就不会报错了
	    } finally {}
	}, 20 ),

	// 创建超时定时器,如果过10秒没检测到加载成功
	_timer2 = setTimeout( function() {
	    clearInterval( _timer1 ); // 清除定时器
	    clearTimeout( _timer2 );
	    callbackFailed();
	}, 10000 );
		}

    // 检测js是否加载完成
		for(var i=0; i<jsElements.length; i++){
		    var node = jsElements[i];
		    node.onload = node.onreadystatechange = function(){
			var loadComplete = true;
			for(var j=0; j<jsElements.length; j++){
			    var nodeTmp = jsElements[i];
			    if (this.readyState && this.readyState != "loaded" && this.readyState != "complete") {
				loadComplete = false;
				break;
			    }
			}
			// js加载完成
			if (loadComplete){
			for(var j=0; j<jsElements.length; j++){
		    var nodeTmp = jsElements[j];
		    nodeTmp.onload = nodeTmp.onreadystatechange = null; //防止IE内存泄漏
		}
		// 检测css的加载情况
		checkCssLoad();
	    }
	}
		}

		return this;
	},

http://www.fantxi.com/blog/archives/load-css-js-callback/

http://nootn.com/blog/Develop/44/

http://blog.filod.net/326.html

http://www.chhua.com/web-note1612

Published in JavaScript

No Responses to “javascript创建css、js,加载完成时触发callback兼容主流浏览器的实现”

Leave a Reply

请输入算式结果(看不清请点击图片)
(必须)