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

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

question 禁止 rules 跨域問題 onload targe 結合 defined finally

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

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

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

  1. <link onload="alert(‘css onload‘)" rel="stylesheet" href="http://localhost/css/123.css"/>
  2. <script onload="alert(‘js onload‘)" src="http://localhost/123.js"></script>
  3. <link onreadystatechange="alert(‘css readystatechange‘)" rel="stylesheet" href="http://localhost/css/123.css"/>
  4. <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創建元素

  1. var doc = document,
  2. head = doc.getElementsByTagName("head")[0],
  3. css, js;
  4. css = doc.createElement(‘link‘);
  5. css.href = ‘http://localhost/123.css?2‘;
  6. css.rel = ‘stylesheet‘;
  7. head.appendChild(css);
  8. js = doc.createElement(‘script‘);
  9. js.src = ‘http://localhost/123.js?2‘;
  10. head.appendChild(js);
  11. css.onload = function(){
  12. alert(‘css onload‘)
  13. }
  14. js.onload = function(){
  15. alert(‘js onload‘)
  16. }
  17. css.onreadystatechange = function(){
  18. alert(‘css readystatechange‘)
  19. //alert(this.readyState) //IE可以得到loading/complete, OP為undefined
  20. }
  21. js.onreadystatechange = function(){
  22. alert(‘js readystatechange‘)
  23. //alert(this.readyState) //IE可以得到loading/loaded, OP為loaded
  24. }

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都要寫上,代碼類似下面:

  1. // 先把js或者css加到頁面: head.appendChild(node);
  2. // onload為IE6-9/OP下創建CSS的時候,或IE9/OP/FF/Webkit下創建JS的時候
  3. // onreadystatechange為IE6-9/OP下創建CSS或JS的時候
  4. node.onload = node.onreadystatechange = function(){
  5. // !this.readyState 為不支持onreadystatechange的情況,或者OP下創建CSS的情況
  6. // this.readyState === "loaded" 為IE/OP下創建JS的時候
  7. // this.readyState === "complete" 為IE下創建CSS的時候
  8. if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") {
  9. node.onload = node.onreadystatechange = null; //防止IE內存泄漏
  10. alert(‘loaded, run callback‘);
  11. }
  12. }

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)。

  1. var doc = document,
  2. head = document.getElementsByTagName( ‘head‘ )[0],
  3. link = doc.createElement(‘link‘);
  4. //link.href = ‘http://xxx.com/123.css‘; //跨域
  5. link.href = ‘http://localhost/123.css‘;
  6. link.rel = ‘stylesheet‘;
  7. head.appendChild( link );
  8. var sheet, cssRules;
  9. if ( ‘sheet‘ in link ) { //FF/CM/OP
  10. sheet = ‘sheet‘; cssRules = ‘cssRules‘;
  11. }
  12. else { //IE
  13. sheet = ‘styleSheet‘; cssRules = ‘rules‘;
  14. }
  15. var _timer1 = setInterval( function() { // 通過定時器檢測css是否加載成功
  16. try {
  17. if ( link[sheet] && link[sheet][cssRules].length ) { // css被成功加載
  18. // console.log(link[sheet][cssRules]);
  19. clearInterval( _timer1 ); // 清除定時器
  20. clearTimeout( _timer2 );
  21. alert(‘loaded, run callback‘); // 加載成功執行callback
  22. }
  23. } catch( e ) {
  24. // FF看到的可能的報錯:
  25. //本地:nsresult: "0x8053000f (NS_ERROR_DOM_INVALID_ACCESS_ERR)" ,因為沒加載完成還不能讀取,加載完畢就不會報錯了
  26. //跨域:Security error, code: "1000" nsresult: "0x805303e8",因為不能跨域讀取CSS。。。
  27. //關於跨域訪問:FF/OP/CM都禁止,IE6-9都可以跨域讀取css。
  28. } finally {}
  29. }, 20 ),
  30. // 創建超時定時器,如果過10秒沒檢測到加載成功
  31. _timer2 = setTimeout( function() {
  32. clearInterval( _timer1 ); // 清除定時器
  33. clearTimeout( _timer2 );
  34. alert(‘loaded ? run callback‘); // 都過了這麽長時間了,雖然沒判斷加載成功也執行callback(這裏可能本身就加載失敗,也可能是跨域的情況)
  35. }, 10000 );
  36. //@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})

  1. var doc = document,
  2. head = doc.getElementsByTagName(‘head‘)[0],
  3. link = doc.createElement(‘link‘);
  4. link.href = ‘http://localhost/123.css‘;
  5. link.rel = ‘stylesheet‘;
  6. head.appendChild( link );
  7. var div = document.createElement(‘div‘);
  8. div.id = ‘hack4loaded‘;
  9. var getStyle = function(name){ //獲取css裏面設置的元素樣式
  10. if(div.currentStyle){ // IE/OP
  11. return div.currentStyle[name];
  12. }else{ // FF/CM
  13. return div.ownerDocument.defaultView.getComputedStyle(div, null)[name];
  14. }
  15. };
  16. document.body.appendChild(div); // 添加到頁面
  17. //創建定時器,讀取創建的div的樣式是否是已經在css裏面設置好的
  18. var _timer1= setInterval( function() {
  19. //console.log(getStyle(‘display‘));
  20. if (‘none‘ === getStyle(‘display‘)) { //樣式和css裏面設置的一樣,說明加載成功
  21. clearInterval( _timer1 ); // 清除定時器
  22. clearTimeout( _timer2 );
  23. div.parentNode.removeChild(div); //移除div
  24. alert(‘loaded, run callback‘); // 加載成功執行callback
  25. }
  26. }, 50 ),
  27. // 創建超時定時器,防止css文件加載失敗的情況
  28. _timer2 = setTimeout( function() {
  29. clearInterval( _timer1 ); // 清除定時器
  30. clearTimeout( _timer2 );
  31. alert(‘fail to load‘); // 這個沒加載成功的可能性很大,再就是網速太慢超時了
  32. }, 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加載完成。

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