专注于前端开发, 追求更好的用户体验, 更好的开发体验 [长沙前端QQ群:234746733]

用javascript预加载图片、css、js的方法研究

预加载的好处可以让网页更快的呈现给用户,缺点就是可能会增加无用的请求(但图片、css、js这些静态文件可以被缓存),如果用户访问的页面里面的css、js、图片被预加载了,用户打开页面的速度会快很多,提升用户体验。在用到一些大图片展示的时候,预加载大图是很不错的方法,图片更快的被呈现给用户。不多说了,作为一个前端攻城师都懂的,下面分享我做的测试和得到的结果。

先说需要知道的服务器返回的status code:
status-code: 200 - 客户端请求成功
status-code: 304 - 文件已经在浏览器缓存中,服务器告诉客户端,原来缓冲的文档还可以继续使用。
本文测试判断文件被是否被缓存,用的就是判断是否返回304.

下面针对预加载的几个方法,在不同的浏览器下加载img/js/css做个测试,主要包括new Image()、object、iframe。以下加载测试的js、css、图片文件,是从几个门户网站找的(为啥找几个?是为了尽可能滴测试到特殊的情况,测试中还真遇到了)。

1、测试用new Image()预加载

1.1、new Image()加载


new Image().src = 'http://img02.taobaocdn.com/tps/i2/T1iQhUXnxpXXXXXXXX-171-48.png'; //淘宝
new Image().src = 'http://static.paipaiimg.com/module/logo/logo_2011_02_22.png'; //拍拍
new Image().src = 'http://co.youa.baidu.com/picture/services/images/logo.png'; //有啊
new Image().src = 'http://img1.t.sinajs.cn/t35/style/images/common/header/logoNew_nocache.png'; //新浪*/

然后再把图片添加到页面内:<img src="xxx" />

加载图片没啥好说的,IE6-9/CM/FF/OP/都返回304,预加载成功。

1.2、测试用new Image()加载css


new Image().src = 'http://a.tbcdn.cn/p/global/1.0/global-min.css'; //淘宝(1)
new Image().src = 'http://static.paipaiimg.com/member/activate.css'; //拍拍(2)
new Image().src = 'http://co.youa.baidu.com/picture/services/base.css'; //有啊(3)
new Image().src = 'http://img1.t.sinajs.cn/t35/skin/skin_008/skin.css'; //新浪(4)
// http://auto.sina.com.cn/css/newstyles.css
// 可以用这个测试IE下Expires设置的时间小于当前时间的情况

再把css添加到页面内

这个有区别了:
CM/OP,都返回304(无论有没有设置Expires)。
FF, 全部返回了200。
IE,1/2/4都返回304,而3返回200。对比返回的HTTP-Header可以发现:1/2/4都设置了Expires过期时间,而3没有设置。
说明IE下缓存需要设置Expires(并且设置的时间要大于当前时间),而FF不支持利用new Image()预加载。

1.3、测试用new Image()加载js


new Image().src = 'http://a.tbcdn.cn/s/kissy/1.1.6/kissy-min.js'; //淘宝(1)
new Image().src = 'http://static.paipaiimg.com/js/pp.noticeBoard.js'; //拍拍(2)
new Image().src = 'http://co.youa.baidu.com/picture/services/cms_core.js'; //有啊(3)
new Image().src = 'http://js.t.sinajs.cn/t35/miniblog/static/js/top.js'; //新浪(4)
new Image().src = 'http://shop.qq.com/act/static/week/fri/bang/day_1_p_0_10.js'; //QQ(5)

再把js添加到页面内。

CM/OP,都返回304
FF,只有5返回了304,也只有5的HTTP-Header最简单(包括:Date、Server、Expires、Cache-Control)。
另外几个的响应头信息内容都比较多,但也都设置了5里面的这几个。找规律,发现另外几个的响应头都有:Content-Type:text/javascript,而5里面没这个。
IE,2/5返回了304,1/3/4返回200,对比响应头,应该还是Content-Type影响的,IE里面2/5都没看到Content-Type。
另外,感谢AndrewZhang(http://www.cnblogs.com/AndyWithPassion/)提到,IE下 image 预加载js在httpwatch下查看content,资源的加载并不是完整的。我这里测试也是如此。貌似有字节限制,测试中2返回的是完整的,5有一部分内容丢失了。所以用new Image加载JS一点都不可取。

这里总结下:预加载图片用new Image()兼容性没问题。但是css/js只有OP/CM可以,IE/FF基本是无效(这点IE/FF到挺有默契)。

2、测试用object预加载

var doc = document,
	obj = doc.createElement('object');
	//obj.data = '123.js'; //Ps: 这样写OP下无效(会把data的内容作为object标签里的text node)
	//obj.setAttribute('data', '123.js'); // img、css、js
	obj.style.cssText = 'position:absolute;top:-1px;width:1px;height:1px;'; 
	// obj.style.width = obj.style.height = 0;
	doc.body.appendChild(obj); // 插入object 标签需要插入到非head部分,以触发加载*/
	//obj.onload = function(){ alert('loaded') }; // FF/OP/Webkit支持(如果data是图片,IE9也可以)

然后再吧object里面data加载的文件,创建标签加到HTML内测试。

测试结果:
FF/OP/CM: 无论是img/js/css,都返回304。
IE6-8:用object加载img/js/css,会直接Aborted。
IE9比较特殊:
IE9加载js/css,先请求并返回HTTP200,再请求并Aborted,这里实际上是请求1次(第2次Aborted了)。
IE9加载img的情况,先请求并返回HTTP200,再请求返回图片,所以图片需要请求2次。

IE9的第1次请求返回的内容是空的(并且此时浏览器一般会卡住,或者直接失去响应)。
IE9首先会请求url,获取文件类型,判断是JS/CSS就Aborted,判断是图片才加载。

至于IE9第1次请求,大概是靠读取HTTP头信息来得到文件类型,或者偷偷把文件下载下来,然后在沙盒里面测试文件类型。
一个有意思的事情,比如用object加载JS,IE9有时也能加载进来,也就是第1个请求没判断出文件是JS(想看到这个要看运气了,貌似网速慢的时候可能发生)

据说以前IE是靠文件后缀来判断文件类型的,后期用HTTP头信息来判断,而他们都可以伪造,所以object在IE下存在安全问题。
IE6/7,如果文件后缀后缀为.js/.css不会发出请求,如果改成http://xxx/test.js?123.png,就发送请求了,然后用script标签引入,发现可被缓存(css这样搞也OK^^)。
IE8,后缀为js/css也不会发出请求,改后缀为png可以发出请求并得到内容,然后页面创建标签引入,文件并没有被缓存。但如果文件是真正的图片就被缓存了。
题外话:通过上面可以发现,随着IE的升级,安全性也越来越高了。

So,这里的结论是:FF/OP/CM下可以用object预加载,IE就千万别用了。

3、测试用iframe预加载

先创建页面a.html,然后加上下面的js。

var doc = document,
	ifm = doc.createElement("iframe");
	//ifm.id="preLoadIfm";
	// ifm.style.border = ifm.width = ifm.height = 0;
	ifm.style.cssText = 'position:absolute;top:-10px;border:0;width:1px;height:1px;';
	ifm.scrolling = "no";
	doc.body.appendChild(ifm);

window.onload = function(){ // 预加载当然最好是window.onload之后触发
	//要触发onload,需要先appendChild,然后再写onload(如果顺序颠倒,IE下不能触发)
	// ifm.onload = function(){ alert('ifm loaded'); }
	// contentWindow.document-所有都支持,contentDocument-IE9/FF/OP/CM支持
	var ifmDoc = ifm.contentDocument || ifm.contentWindow.document;
	ifmDoc.open();
	ifmDoc.write('<!doctype><html><head></head><body>');
	//ifmDoc.write('<style>html{background:#000;color:#fff}</style>'); // 用于测试
	//ifmDoc.write('<script>alert("a")<\/script>'); // 用于测试
	//ifmDoc.write('<p>test</p><p>test</p><p>test</p><p>test</p><p>test</p>');// 用于测试
	// 开始加载
	ifmDoc.write('<link rel="stylesheet" href="http://localhost/123.css?2011" />');
	ifmDoc.write('<script defer src="http://localhost/123.js?2011"><\/script>'); //不加defer,你会发现IE卡死。。
	ifmDoc.write('<img width="1" height="1" src="http://localhost/123.png?2011" />');
	ifmDoc.write('</body></html>');
	ifmDoc.close();
};

然后创建新页面b.html,把要上面预加载的文件加到html里面,测试是否已经预加载。
结果:IE/FF/OP/CM都成功预加载。

需要说明的是:当打开a.html后,再刷新页面后,iframe内加载文件的情况。
FF,返回200(注意,这个200不是服务器返回的200,是请求缓存成功。因为发送请求的时间显示的是0)。
CM,显示状态是(from cache).
OP,虽然显示状态是n/a,但是也是from cache。
IE,IE自带的调试工具显示304,HttpWatch显示from cache。

测试环境:
WIN7 EN SP1:OP 11.50、IE7-9、FF 3.6/6.0、Chrome 10
XP EN SP3:IE6
XP EN SP3:IE7
XP CN SP3:IE8
工具:IE9自带的调试工具、HttpWatch、firebug、chrome自带的调试工具、Opera Dragonfly。

最后得出的结论:js预加载图片使用new Image()基本够用了。但是css、js特殊一些,使用object需要判断浏览器。如果考虑到js、css、img都能兼容实现预加载,可以考虑使用iframe。

另外,上面的方法创建iframe后,不使用write()写入要加载的文件,直接设置iframe.src = "cache.html",然后把要预加载的文件写在cache.html内也是可行的(以前看过有文章介绍新浪微博是这样做的,但是文章地址找不到了,搜索也没搜到),cache的网址我收藏了:http://tjs.sjs.sinajs.cn/miniblog2/static/html/cache.html,但是看微博的首页没找到这个,不知道在哪个页使用的。

其他预加载的一点补充

doc.createElement('script') 可以预加载js,如果js里面有对页面的操作,就会对页面产生影响。
doc.createElement('link') 可以预加载css,但是对当前页面的样式也可能会有影响。
所以这样预加载不太可取。
用ajax加载img/js/css,兼容性不错,文件可以被缓存,但是只能限制同域,所以使用范围有限。
预加载图片还可以利用CSS的背景图片实现。牛人lifesinger之前写过关于图片的HTTP请求的文章,不过他博客以前的数据没了。网上搜索到一篇转载的:http://www.cnblogs.com/mofish/archive/2011/01/18/1938570.html。
文章里面提到了用背景图和隐藏的img标签来预加载,调理很清晰。也可以作为参考。

另外,模仿新浪的cache.html自己写了个,如果喜欢把iframe作为独立文件使用的可以作为参考。

<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<script>
//usage: cache.html?v=123
var win = window,
	doc = document,
	head = doc.getElementsByTagName("head")[0],
	getQuery = function(){
		var ret = {},
			sch = win.location.search,
			arr,
			tmp;
		if (sch) {
			sch = sch.substr(1);
			arr = sch.split("&");
			for(var i = 0, j = arr.length; i < j; i++) {
				tmp = arr[i].split('=');
				ret[tmp[0]] = tmp[1];
			}
		}
		return ret;
	},
	version = getQuery().v || '';

win.onerror = function(){return true}; //屏蔽js错误提示

win.onload = function(){
	var b = doc.createElement("script");
	b.src = 'http://xx/1.js?v=' + version;
	head.appendChild(b);
	//...
};
doc.write('<link rel="stylesheet" href="http://xxx/3.css?version=' + version + '" \/>');
</script>
<img src="http://xxx/4.png" />
</body></html>

/ 分类: 开发,实践 / TrackBackhttps://xhl.me/archives/preload-images-css-js/trackback标签: css, javascript, preload

已有 10 条评论 »

  1. AndrewZhang AndrewZhang

    看了你的这篇文章很受启发,thanks a lot,buddy : )

  2. AndrewZhang AndrewZhang

    对了 我测了下IE下 image 预加载js 我用httpwatch professional看的 ,如果你也安装了,可以顺便测下,看下content和stream,资源的加载并不是完整的

    1. kairyou kairyou

      THX,我测试也是这样。已经在文章加上说明了。

      1. AndyYang AndyYang

        测试盖楼!

  3. SteveZheng SteveZheng

    非常好的一篇文章呀。我也在思考预加载的这个问题,很受启发。谢谢。

  4. artshell artshell

    启发性很大啊! thanks!

  5. 小谢 小谢

    3、测试用iframe预加载 这种方法根本不行在FF下

    1. kairyou kairyou

      @小谢
      我用最新的FF测试也OK的, 不知道你是?

  6. dick dick

    用iframe预加载跨域的js在ie下会弹出安全提示

  7. yizishou yizishou

    谢谢!放收藏夹里了

添加新评论 »