`
cloudgamer
  • 浏览: 59469 次
  • 性别: Icon_minigender_1
  • 来自: 顺德
社区版块
存档分类
最新评论

图片延迟加载(按需加载)效果

阅读更多

之前在做一个图片浏览效果时,要看后面的小图必须等到前面的加载完,而且大图的位置是在大量的小图后面,导致大图要等到小图都加载完才能显示,为了解决这个问题,就想到了Lazyload效果。
现在很多网站都用了类似的效果,如淘宝、Bing等。
这个图片延迟加载效果是在Lazyload的基础上扩展的,主要扩展了获取img元素,获取src和图片加载的部分。

兼容:ie6/7/8, firefox 3.5.5, opera 10.10, safari 4.0.4, chrome 3.0
其中safari和chrome部分功能不支持。

 

效果图

 


 
在线效果预览

 

程序说明

【获取图片】

先定义filter函数作为筛选程序,
var getSrc = opt.getSrc,
    filter = $$F.bind( this._filter, this,
            opt["class"],
            getSrc ? function(img){ return getSrc(img); }
                : function(img){ return img.getAttribute( attribute ) || img.src; },
            opt.holder
        );
然后用这个filter函数筛选出需要的图片集合:
this._elems = $$A.filter(
        opt.images || container.getElementsByTagName("img"), filter
    );
如果要自定义图片集合可以在程序可选参数的images属性来设置,否则自动从容器获取img元素作为图片集合。

这里的filter其实是包装了筛选样式cls、获取src的方法getSrc和占位图holder三个参数的_filter筛选程序。
在_filter程序中,会对图片集合进行筛选和整理。
如果自定义了"class"筛选样式,会自动排除样式不对应的图片:
if ( cls && img.className !== cls ) return false;

再用getSrc获取原图地址,即实际要显示的图片地址。
如果有自定义getSrc会优先使用。
没有的话,再通过保存原图地址的_attribute自定义属性从元素获取。
最后才直接从元素的src属性获取。

接着排除src不存在的:
if ( !src ) return false;

要注意处理原图地址就是元素当前src的情况:
if ( src == img.src ) {
    if ( img.complete || $$B.chrome || $$B.safari ) return false;
    img.removeAttribute("src");
}
如果complete为true,说明图片已经载入完成了,可以排除;
如果是chrome或safari,不能取消当前加载,所以也排除掉(具体看图片的HTTP请求部分)。
否则,用removeAttribute移除src属性来取消图片当前的加载。

如果设置了holder占位图,就重新设置图片src:
if ( holder ) { img.src = holder; }

最后把原图地址记录到元素的_attribute自定义属性中:
img.setAttribute( this._attribute, src );

逐个图片元素筛选整理后,就得到要加载的图片集合了。


【图片加载】

ImagesLazyLoad相比LazyLoad,已经实现了_onLoadData加载程序,不需要再自己定义加载。
在_onLoadData程序中,主要是用来显示图片。

先用判断是否有_attribute自定义属性:
if ( this._hasAttribute( img ) ) {
    ...
}
在_hasAttribute方法中是这样判断的:
this._hasAttribute = $$B.ie6 || $$B.ie7
    ? function(img){ return attribute in img; }
    : function(img){ return img.hasAttribute( attribute ); };
由于ie6/7跟其他浏览器对attribute和property的理解不同,所以要分开处理,详细参考这里的attribute/property
为了保证兼容性,程序会优先使用attribute的方式来操作自定义属性。

当img有_attribute自定义属性时,就用getAttribute来获取原图地址,并设置img的src,在用removeAttribute来移除自定义属性。
移除的意义在于,当有多个实例使用同一个元素时,能保证图片加载一次后就不会重复加载,即防止实例间的冲突。


【图片的HTTP请求】

这里说说开发过程中发现的一些关于图片加载的问题。

首先是加载空字符串的问题,如果给img的src设为空字符串的话,可能会得到意料之外的结果。
例如在 http://xxx/test.htm 里面的 <img src=""> 会发生以下情况:
ie 会产生相对地址的请求,即:http://xxx/
Safari/Chrome 会产生当前页面地址的请求,即:http://xxx/test.htm
Opera/Firefox 不会产生请求
详细参考Nicholas C. Zakas的“Empty image src can destroy your site”。
如果不想加载图片,不应该把src设为空值,因为还可能会发出请求,浪费资源。
可以像程序那样,通过removeAttribute来移除就行了。

还有一个问题是在Safari和Chrome,由于webkit内核的bug,正在加载的图片并不能取消加载。
所以程序在取消图片加载的部分,如果是Safari或Chrome会继续加载,不进行延迟。
这个问题最初从lifesinger的datalazyload的说明部分看到的,具体可以自己用Fiddler来测试。

更多相关资料可以参考lifesinger的“图片的HTTP请求”。


【继承结构】

在发布的程序中,这是第一个用了继承的,本人平时也没怎么用到,所以还不成熟,算是试试水吧。
程序用wrapper来做继承,详细参考工具库的说明。

先用wrapper给ImagesLazyLoad包装(继承)LazyLoad:
var ImagesLazyLoad = $$.wrapper(function(options) {
    ...
}, LazyLoad);
再用extend扩展prototype,添加子类的方法函数:
$$.extend( ImagesLazyLoad.prototype, {
  ...
});
其中_initialize方法用来设置子类属性,由于覆盖了父类的同名方法,所以要通过LazyLoad.prototype._initialize来调用,还要注意用call来修正this。

还有_setOptions方法用来设置子类的可选属性:
return LazyLoad.prototype._setOptions.call(this, $$.extend({
    ...
}, $$.extend( options, {
    onLoadData:    this._onLoadData
})));
子类的_setOptions方法也覆盖了父类的方法,解决方法同_initialize。
其中第一个参数是子类的可选属性,第二个参数是子类定义的属性,即不再是可选而是由程序来定义的属性。

总体来说,这是个简陋的继承,等以后积累了一定经验再来扩展吧。


使用技巧

【设置src】

有几个方法可以设置原图地址:
1,正常设置src:渐进增强,不支持js时也能显示,但chrome和safari有bug,不支持这种方式;
2,把原图地址设置到自定义属性中:所有浏览器都兼容,但在不支持js时图片不能显示;
3,用自定义函数获取:使用在比较复杂的情况,需要手动设置。
具体还是要根据实际情况来选择。

【设置holder】

如果使用了holder占位图,程序会自动设置图片元素显示占位图。
推荐使用loading图片来设置,但loading图往往跟原图的尺寸是不同的。
如果img设置了原图宽高,又想保持loading图的尺寸,把它设为背景就可以了。
但这样在ie下,默认会有一个小图标(不设置src)。
要去掉这个小图标可以设置holder为一个透明图片的链接,或者参考这里的TRANSPARENT“做”一个透明图片。
实例中也是这样设置的,可以参考一下。

【执行程序】

千万不能在window.onload中执行,因为那时图片都已经加载完了。
而应该在容器后面(window的话是文档结尾)或DOMContentLoaded中执行。
 
程序源码
var ImagesLazyLoad = $$.wrapper(function(options) {
	this._initialize( options );
	//如果没有元素就退出
	if ( this.isFinish() ) return;
	//初始化模式设置
	this._initMode();
	//进行第一次触发
	this.resize(true);
}, LazyLoad);

$$.extend( ImagesLazyLoad.prototype, {
  //初始化程序
  _initialize: function(options) {
	LazyLoad.prototype._initialize.call(this, [], options);
	//设置子类属性
	var opt = this.options;
	this.onLoad = opt.onLoad;
	var attribute = this._attribute = opt.attribute;
	//设置加载图片集合
	var getSrc = opt.getSrc,
		filter = $$F.bind( this._filter, this,
				opt["class"],
				getSrc ? function(img){ return getSrc(img); }
					: function(img){ return img.getAttribute( attribute ) || img.src; },
				opt.holder
			);
	this._elems = $$A.filter(
			opt.images || this._container.getElementsByTagName("img"), filter
		);
	//判断属性是否已经加载的方法
	this._hasAttribute = $$B.ie6 || $$B.ie7
		? function(img){ return attribute in img; }
		: function(img){ return img.hasAttribute( attribute ); };
  },
  //设置默认属性
  _setOptions: function(options) {
	return LazyLoad.prototype._setOptions.call(this, $$.extend({//默认值
		images:		undefined,//图片集合
		attribute:	"_lazysrc",//保存原图地址的自定义属性
		holder:		"",//占位图
		"class":	"",//筛选样式
		getSrc:		undefined,//获取原图地址程序
		onLoad:		function(){}//加载时执行
	}, $$.extend( options, {
		onLoadData:	this._onLoadData
	})));
  },
  //筛选整理图片对象
  _filter: function(cls, getSrc, holder, img) {
	if ( cls && img.className !== cls ) return false;//排除样式不对应的
	//获取原图地址
	var src = getSrc(img);
	if ( !src ) return false;//排除src不存在的
	if ( src == img.src ) {
		//排除已经加载或不能停止加载的
		if ( img.complete || $$B.chrome || $$B.safari ) return false;
		img.removeAttribute("src");//移除src
	}
	if ( holder ) { img.src = holder; }
	//用自定义属性记录原图地址
	img.setAttribute( this._attribute, src );
	return true;
  },
  //显示图片
  _onLoadData: function(img) {
	var attribute = this._attribute;
	if ( this._hasAttribute( img ) ) {
		img.src = img.getAttribute( attribute );
		img.removeAttribute( attribute );
		this.onLoad( img );
	}
  }
});
 
  • 大小: 37.7 KB
分享到:
评论
14 楼 lwkjob 2012-06-14  
非常强大谢谢
13 楼 cloudgamer 2010-03-20  
gundumw100 写道
好像jQuery也有这样的一个插件


是啊
我也参考看了jq的效果
12 楼 willko 2010-03-18  
杯具了。好多人都说没问题。。
我环境是 10.50 win7

我再看看

11 楼 cloudgamer 2010-03-17  
willko 写道
cloudgamer 写道
willko 写道
1年前写了一个用来延迟ajax请求的。。。。

原理也是这样的吗

嘻嘻,差不多,就是没这么强大。

我做这个遇到了点bug,在opera下

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" dir="ltr"> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 
    <title>JavaEye Java编程 Spring框架 AJAX技术 Agile敏捷软件开发 ruby on rails实践-JavaEye做最棒的软件开发交流社区</title> 
	<script type="text/javascript">
window.onload = function() {
document.body.onclick = function() {
alert(document.documentElement.scrollTop + "-" + document.body.scrollTop);
}
}
	</script>
  </head> 

  <body> 

<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~请移动到此,刷新</p>
  </body>
  </html>


1.先滚动到底边
2.刷新
3.点击页面

弹出的scrollTop在opera下是0,必须再滚动一次,才有值

我用10.10测试
貌似没问题
10 楼 willko 2010-03-17  
cloudgamer 写道
willko 写道
1年前写了一个用来延迟ajax请求的。。。。

原理也是这样的吗

嘻嘻,差不多,就是没这么强大。

我做这个遇到了点bug,在opera下

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" dir="ltr"> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 
    <title>JavaEye Java编程 Spring框架 AJAX技术 Agile敏捷软件开发 ruby on rails实践-JavaEye做最棒的软件开发交流社区</title> 
	<script type="text/javascript">
window.onload = function() {
document.body.onclick = function() {
alert(document.documentElement.scrollTop + "-" + document.body.scrollTop);
}
}
	</script>
  </head> 

  <body> 

<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~</p><p>~</p>
<p>~</p>
<p>~请移动到此,刷新</p>
  </body>
  </html>


1.先滚动到底边
2.刷新
3.点击页面

弹出的scrollTop在opera下是0,必须再滚动一次,才有值
9 楼 gundumw100 2010-03-17  
好像jQuery也有这样的一个插件
8 楼 cloudgamer 2010-03-17  
willko 写道
1年前写了一个用来延迟ajax请求的。。。。

原理也是这样的吗
7 楼 willko 2010-03-16  
1年前写了一个用来延迟ajax请求的。。。。
6 楼 cloudgamer 2010-03-16  
felsenlee 写道
图中的美女很不错!!
又看到你的新帖了,很实用的功能。
如果是大小不一的图片,会不会很难看?

这个看怎么处理咯
不过跟加载本身没什么关系
5 楼 cloudgamer 2010-03-16  
抛出异常的爱 写道
我研究过picsca的播放....
它不会一次生成所有的图片格子

就是显示用的5个+2个
左边一个右边一个
每次把最左边已经盖掉的那个图片
改变其url和左margrn使其放到最后一个被盖的图片之后
再改变整个div margrn多减一个图片距离
整个就向左动了一格.

当margrn大于5000多的一个点时他会弹出刷新div距离就清0了...


这也是按需加载的一种,不过使用限制比较多
如果我想一下子拖到后面看的话可能会有问题
而且我这个不一定就用在图片浏览的
4 楼 抛出异常的爱 2010-03-16  
我研究过picsca的播放....
它不会一次生成所有的图片格子

就是显示用的5个+2个
左边一个右边一个
每次把最左边已经盖掉的那个图片
改变其url和左margrn使其放到最后一个被盖的图片之后
再改变整个div margrn多减一个图片距离
整个就向左动了一格.

当margrn大于5000多的一个点时他会弹出刷新div距离就清0了...
3 楼 felsenlee 2010-03-15  
图中的美女很不错!!
又看到你的新帖了,很实用的功能。
如果是大小不一的图片,会不会很难看?
2 楼 cloudgamer 2010-03-15  
Qiao.Gbin 写道
前天刚做了一个图片浏览的,就是图片加载太慢了.^_^
研究一下.

有什么问题可以一起讨论
1 楼 Qiao.Gbin 2010-03-12  
前天刚做了一个图片浏览的,就是图片加载太慢了.^_^
研究一下.

相关推荐

Global site tag (gtag.js) - Google Analytics