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{width:200px;height:200px;}
#load{width: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属性是否存。