js 外部文件加载处理

概述

前端在日常工作中很大一部分时间是在思考页面的优化方案,让页面载入得更快。鉴于javascript是单线程的事件驱动语言,优化工作之一就是:控制图片、swf、iframe等大流量文件以及js和css等文件的加载顺序,让它们井然有序的进入到页面中,页面就能尽可能完整的呈现在他们眼前。而为了更好的用户体验,我们要知道每个文件触发onload事件的方案,因为它们在各个浏览器中的表现不尽相同。

iframe的 load 事件

在所有为IFRAME动态添加onload监听事件的方法中,只有 使用事件监听方式为 IFRAME 的 onload 事件绑定处理函数,IE6、7、8才有效。所以为 IFRAME 添加load事件完美方案如下:

 1 // 事件监听兼容方案
 2 function addEvent(elem,event,fn){
 3     if (elem.attachEvent) {
 4         elem.attachEvent('on'+event,fn)
 5     } else {
 6         elem.addEventListener(event,fn,false)
 7     }
 8 }   
 9 
10 window.onload = function(){
11     var iframeA = document.createElement('iframe');
12     iframeA.src = 'http://www.baidu.com'
13     addEvent(iframeA,'load',function(){
14         document.body.bgColor = '#000'; // 回调函数
15     });
16     document.body.appendChild(iframeA);
17 }

优化页面建议不要嵌套iframe,但是在内部项目还是很常见。其实在IE中,监控iframe加载完毕还可以采取监听 onreadystatechange 事件。

flash 的 load 事件

解决flash的 load主要是两个问题:获取flash对象和flash何时加载完毕。

首先第一个问题:如果object和embed用同样的ID,获取flash对象的时候,IE会认不出。解决方案:

  • js判断IE和非IE,IE中是object,非IE中是embed。
  • 通过flash对象的PercentLoaded方法,检测其值是否为100。

html代码

<div id="load">flash加载中....</div>
<div id="swfWrap"></div>

css代码

#swfWrap{200px;height:200px;}
#load{200px;color:#fff;text-align:center;background-color:#eee;}

js代码

 1 // 封装通过ID获取
 2 function $(id){
 3     return document.getElementById(id)
 4 }
 5 
 6 var isIE = navigator.appVersion.indexOf("MSIE") != -1 ? true: false;
 7 
 8 
 9 // 监听flash是否加载成功
10 function listenMovie(flash){
11     try{
12         return Math.floor(flash.PercentLoaded()) == 100 ;
13     }catch(e){
14         return false;
15     }
16 }
17 
18 // 获取FLASH对象
19 function thisMovie(movieName) {
20     if (isIE) {
21         return window[movieName];
22     }
23     else {
24         return document[movieName];
25     }
26 }
27 
28 // 创建flash
29 function createFlash(id,url){
30     var html = '<object id="flash" height="200" width="200" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">'+
31                '<PARAM NAME="FlashVars" VALUE="">'+ 
32                 '<PARAM NAME="Movie" VALUE="'+url+'">'+ 
33                 '<PARAM NAME="WMode" VALUE="Transparent">'+ 
34                 '<PARAM NAME="Quality" VALUE="High">'+  
35                 '<PARAM NAME="AllowScriptAccess" VALUE="always">'+  
36                 '<embed type="application/x-shockwave-flash" src="'+url+'" id="flashFF" name="flashFF" wmode="window" quality="high" width="200" height="200"></embed>'+
37                 '</object>';
38     $(id).innerHTML = html;
39 }
40 
41 window.onload = function(){
42     createFlash('swfWrap','flips2.swf')
43     var flashObj = isIE ? thisMovie("flash") : thisMovie("flashFF");
44     var intervalID =  setInterval(function(){
45         if (listenMovie(flashObj)) {
46             clearInterval(intervalID);
47             intervalID = null;
48             $('load').innerHTML = 'flash加载完毕';
49         }
50     },60)
51 }

其中object中的 classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" 不能去掉,不然IE6下会获取取不到flash对象;embed的name值也不能去掉,不然chrome也获取不到。

这里再提下flash的通信的问题,可以参考 这里,解决方法就是将EMBED的 swliveconnect 属性设置成true,然后就可以跟flash通信了。

IMG的 load 事件

img的load事件,我们使用 new Image()。这里我们得注意 complete 事件。研究网上的得出以下代码:

 1 var img = new Image(); 
 2 img.src= "http://i1.hoopchina.com.cn/user/627/17191627/17191627_big_3.jpg";
 3 if (img.complete || img.width) {
 4     alert("该图片已经在缓存中,不需要再下载")
 5     alert(img.height)
 6 } else {
 7     img.onload = function() {
 8         alert("图片加载完成");
 9         alert(img.height)   
10     }
11 }

这里网上很多说法是这样子说的,只要加载过一次图片,img.complete就变成true了,图片就存进浏览器缓存,下次再加载就直接忽略了onload事件,直接从缓存里去读取,而不是再重新去下载。但是我在多个变化条件下(同一个浏览器、同一个标签页、清楚缓存、一个页面存在多个相同图片)测试发现:

在f5刷新后,除了火狐是直接从缓存中读取的,也就是执行 if(img.complete || img.width)语句下的,其他的浏览器都是执行else语句里的代码,重新下载图片;ctrl+f5的话,则所有浏览器都是重新下载图片的。

那么 img.complete 的真正意义在于什么呢?一张页面中,如果存在多个图片地址相同的 img 标签 ,浏览器只会请求一次图片链接,而不是每个img都去请求。

使用 new Image() 请求相同的 gif 图片时,img.complete 貌似不准确,不知道什么原因,难道是因为 gif 动态图是由多张静态图组成?。

而网上说的,将src赋值放在onload事件之后,并不是从根本原因上解决问题。

JS的 load 事件

首先准备一下即将要用到的辅助函数:

 1 function delay_file(url) {
 2 
 3     var type = url.split('.'),
 4         file = type[type.length - 1];
 5 
 6     if (file == 'css') {
 7         var obj = document.createElement('link'),
 8             lnk = 'href',
 9             tp = 'text/css';
10         obj.setAttribute('rel', 'stylesheet');
11     } else {
12         var obj = document.createElement('script'),
13             lnk = 'src',
14             tp = 'text/javascript'; 
15     }
16 
17     obj.setAttribute(lnk, url);
18     obj.setAttribute('type', tp);
19     file == 'css' ? document.getElementsByTagName('head')[0].appendChild(obj) : document.body.appendChild(obj);
20     return obj;
21 
22 };  

考虑到js加载的特殊性,浏览器引擎在解析js时,对其他资源和文档都会停止。所以我们采用以上方法来异步加载js。而如果想给它增加 回调函数 呢?非IE下 onload 是完美支持的,IE下我们则用 onreadystatechange 事件监听 readyState 值变化。

 1  function loadjs(url, callback) {
 2 
 3     var elem = delay_file(url);
 4     var isIE = navigator.userAgent.indexOf('MSIE') == -1 ? false : true;
 5 
 6     if ( isIE ) {
 7         elem.onreadystatechange = function() {
 8             if (this.readyState && this.readyState == 'loading') return;
 9             if (callback) {
10                 callback(); 
11             }
12         };
13     } else {
14         elem.onload = function() {
15             if (callback) {
16                 callback(); 
17             }
18         };  
19     }
20 
21 }

CSS的 load 事件

CSS 的load事件跟以上讲的 onload 事件兼容性却是相反的,其他浏览器不支持 load 事件,在IE浏览器中反而是支持的。那怎么办呢?

seajs给出了一个方案:

 1 function loadcss(url, callback) {
 2 
 3     var elem = delay_file(url);
 4 
 5     if (elem.attachEvent) {
 6         elem.attachEvent('onload', callback);
 7     } else {
 8         setTimeout(function() {
 9             poll(elem, callback);
10         }, 0);
11     }
12 
13     function poll(_elem, callback) {
14 
15         var isLoaded = false;
16         var sheet = _elem['sheet'];
17         var isOldWebKit = (navigator.userAgent.replace(/.*AppleWebKit/(d+)..*/, '$1')) * 1 < 536;
18 
19         if (isOldWebKit) { //webkit 版本小于 536
20             if (sheet) {
21                 isLoaded = true;
22             }
23         } else if (sheet) {
24             try {
25                 if (sheet.cssRules) {
26                     isLoaded = true;
27                 }
28             } catch (ex) {
29                 if (ex.code === 'NS_ERROR_DOM_SECURITY_ERR') {
30                     isLoaded = true;
31                 }
32             }
33         }
34 
35         if (isLoaded) {
36             setTimeout(function() {
37                 callback();
38             }, 1);
39         } else {
40             setTimeout(function() {
41                 poll(_elem, callback);
42             }, 1);
43         }
44     }
45 
46 }

貌似linkNode在加载前后 linkNode.sheet 和 linkNode.sheet.cssRules 的值会发生变化。我觉得还有一个方法虽然有点绕,但是也是最有效的方法:检测某个类名下的CSS属性是否存。

原文地址:https://www.cnblogs.com/zoucaitou/p/4170107.html