`
tree_161219
  • 浏览: 87800 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

页面图片实现Lazyload 延迟加载效果

阅读更多
出处:http://www.cnblogs.com/cloudgamer/

Lazyload是通过延迟加载来实现按需加载,达到节省资源,加快浏览速度的目的。
网上也有不少类似的效果,这个Lazyload主要特点是:
支持使用window(窗口)或元素作为容器对象;
对静态(位置大小不变)元素做了大量的优化;
支持垂直、水平或同时两个方向的延迟。
由于内容比较多,下一篇再介绍图片延迟加载效果。
兼容:ie6/7/8, firefox 3.5.5, opera 10.10, safari 4.0.4, chrome 3.0


程序说明

【基本原理】

首先要有一个_container容器对象,容器里面是_elems加载对象集合的元素。
停止或不加载集合里面的元素(隐藏或替换等方法)。
然后历遍集合元素,当元素在加载范围内,再进行加载。
加载范围一般是容器的视框范围,即浏览者的视觉范围内。
当容器滚动或大小改变时,再重新历遍元素判断。
如此重复,直到所有元素都加载后就完成。


【容器对象】

程序一开始先用_initContainer程序初始化容器对象。
先判断使用的是window(窗口)还是一般元素作为容器对象:
var container = this._container,
    isWindow = container == window || container == document
            || !container.tagName || (/^(?:body|html)$/i).test( container.tagName );


如果是window,再根据文档渲染模式选择对应的文档对象:
if ( isWindow ) {
    var doc = document;
    container = doc.compatMode == 'CSS1Compat' ? doc.documentElement : doc.body;
}


定义好执行方法后,再绑定scroll和resize事件:
this._binder = isWindow ? window : container;

$$E.addEvent( this._binder, "scroll", this.delayLoad );
isWindow && $$E.addEvent( this._binder, "resize", this.delayResize );


如果是window作为容器,需要绑定到window对象上,为了方便移除用了_binder属性来保存绑定对象。


【加载数据】

当容器滚动或大小改变时,就会通过事件绑定(例如scroll/resize)自动执行_load加载程序。
ps:如果不能绑定事件(如resize),应手动设置执行。

当容器大小改变(resize)时,还需要先执行_getContainerRect程序获取视框范围。
要获取视框范围,一般元素可以通过_getRect方位参数获取程序来获取。
但如果容器是window就麻烦一点,测试以下代码:
代码
<!doctype html>
<style>html,body{border:5px solid #06F;}</style>
<body>
<div style="border:1px solid #000;height:2000px;"></div>
</body>
</html>
<script>
alert(document.documentElement.offsetHeight)
</script>


在ie会得到想要的结果,但其他浏览器得到的是文档本身的高度。
所以在_getContainerRect程序中,其他浏览器要用innerWidth/innerHeight来获取:
代码
this._getContainerRect = isWindow && ( "innerHeight" in window )
    ? function(){ return {
            "left":    0, "right":    window.innerWidth,
            "top":    0, "bottom":window.innerHeight
        }}
    : function(){ return oThis._getRect(container); };

ps:更多相关信息可以看“Finding the size of the browser window”。

在_load程序中,先根据位置参数、滚动值和阈值计算_range加载范围参数:
代码
var rect = this._rect, scroll = this._getScroll(),
    left = scroll.left, top = scroll.top,
    threshold = Math.max( 0, this.threshold | 0 );

this._range = {
    top:    rect.top + top - threshold,
    bottom:    rect.bottom + top + threshold,
    left:    rect.left + left - threshold,
    right:    rect.right + left + threshold
}


在_getScroll获取scroll值程序中,如果是document时会通过$$D来获取,详细看这里dom部分。
threshold阈值的作用是在视框范围的基础上增大加载范围,实现类似预加载的功能。
最后执行_loadData数据加载程序。


【加载模式】

程序初始化时会执行_initMode初始化模式设置程序。
根据mode的设置,选择加载模式:
代码
switch ( this.options.mode.toLowerCase() ) {
    case "vertical" :
        this._initStatic( "vertical", "vertical" );
        break;
    case "horizontal" :
        this._initStatic( "horizontal", "horizontal" );
        break;
    case "cross" :
    case "cross-vertical" :
        this._initStatic( "cross", "vertical" );
        break;
    case "cross-horizontal" :
        this._initStatic( "cross", "horizontal" );
        break;
    case "dynamic" ://动态加载
    default :
        this._loadData = this._loadDynamic;
}


包括以下几种模式:
vertical:垂直方向加载模式
horizontal:水平方向加载模式
cross/cross-vertical:垂直正交方向加载模式
cross-horizontal:水平正交方向加载模式
dynamic:动态加载模式
其中前4种属于静态加载模式,适用于加载对象集合元素的位置(相对容器)或大小不会改变(包括加载后)的情况。
其中后两种又属于"cross"模式,适用于两个方向都需要判断的情况。
程序会对静态加载的情况尽可能做优化,所以应该优先选择静态加载模式。
而动态加载就没有以上约束,但也没有特别的优化。


【动态加载】

动态加载是使用_loadDynamic程序作为加载程序的:
this._elems = $$A.filter( this._elems, function( elem ) {
        return !this._insideRange( elem );
    }, this );


程序会用_insideRange程序来判断元素是否在加载范围内,并用filter筛选出加载范围外的元素,重新设置加载集合。

在_insideRange程序中,先用元素位置和加载范围参数作比较,判断出元素是否在加载范围内:
代码
var range = this._range, rect = elem._rect || this._getRect(elem),
    insideH = rect.right >= range.left && rect.left <= range.right,
    insideV = rect.bottom >= range.top && rect.top <= range.bottom,
    inside = {
            "horizontal":    insideH,
            "vertical":        insideV,
            "cross":        insideH && insideV
        }[ mode || "cross" ];


在动态加载中,不会为元素记录位置参数,所以每次都会用_getRect程序获取加载元素的位置信息。
动态加载会默认使用"cross"模式来判断,即水平和垂直方向都判断。
如果元素在加载范围内,会执行_onLoadData自定义加载程序,进行元素的加载。


【静态加载】

静态加载是程序的重点,也是程序的主要特色。
主要是利用集合元素位置大小固定的性质进行优化,用这个方式会大大提高程序执行效率,越多加载项会越明显。

原理是对加载集合进行排序,转换成有序集合,这样加载范围内的元素总是加载集合中连续的一段。
即可以把加载集合分成3部分,在加载范围前面的,在加载范围内的和加载范围后面的。
以horizontal模式左右滚动为例,加载过程大致如下:
1,记录每个元素的位置参数,按left坐标的大小对加载集合进行排序(从小到大),设置强制加载,跳到1.1;
1.1,记录加载范围,如果是强制加载,跳到1.2,否则跳到2;
1.2,设置索引为0,跳到3;
2,判断滚动的方向,如果向右滚动跳到3,否则跳到4,没有滚动的话取消执行;
3,向后历遍元素,判断元素是否在加载范围内,是的话跳到3.1,否则跳到3.2,如果没有元素,跳到6;
3.1,加载当前元素,并把它从集合中移除,跳回3;
3.2,判断元素的left是否大于容器的right,是的话跳到5,否则跳回3;
4,向前历遍元素,判断元素是否在加载范围内,是的话跳到4.1,否则跳到4.2,如果没有元素,跳到6;
4.1,加载当前元素,并把它从集合中移除,跳回4;
4.2,判断元素的right是否大于容器的left,是的话跳到5,否则跳回4;
5,当前元素已经超过了加载范围,不用继续历遍,跳到6;
6,合并未加载的元素,并记录当前索引,等待滚动,如果全部元素都加载了,就完成退出。
7,当容器滚动时,跳到1.1;当容器大小改变时,设置强制加载,跳到1.1;当容器位置发生变化时,需要重新修正元素坐标,跳到1;

首先加载元素会在_rect属性中记录位置参数,不用重复获取,是一个优化。
更关键的地方是每次滚动只需对上一次索引到加载范围内的元素进行判断,大大减少了判断次数。
大致理解了原理后,后面再详细分析。

在_initMode模式设置中,对静态加载的情况会调用_initStatic初始化静态加载程序。
并传递两个参数mode(模式)和direction(方向)。
根据方向判断方式分三种模式:"vertical"(垂直)、"horizontal"(水平)和"cross"(正交)。
这里先分析一下前两种模式。

在_initStatic程序中,先根据direction设置排序函数,再设置_setElems重置元素集合程序:
代码
var pos = isVertical ? "top" : "left",
    sortFunction = function( x, y ) { return x._rect[ pos ] - y._rect[ pos ]; },
    getRect = function( elem ) { elem._rect = this._getRect(elem); return elem; };
this._setElems = function() {
    this._elems = $$A.map( this._elems, getRect, this ).sort( sortFunction );
};


其中_setElems有两个意义,一个是记录元素的坐标参数,还有是把加载集合用map转换成数组并排序。
因为自定义的加载集合有可以是NodeList,而用sort就必须先把它转换成数组。

最后设置_loadData加载函数:
代码
this._loadData = $$F.bind( this._loadStatic, this,
    "_" + mode + "Direction",
    $$F.bind( this._outofRange, this, mode, "_" + direction + "BeforeRange" ),
    $$F.bind( this._outofRange, this, mode, "_" + direction + "AfterRange" ) );


其中_loadStatic静态加载程序是程序的核心部分,更关键的优化就在这里。
这里还给它包装了三个参数:
direction:方向获取的程序名;
beforeRange:判断是否超过加载范围前面的程序;
afterRange:判断是否超过加载范围后面的程序。
通过包装,除了方便参数的使用,还能使程序结构更加清晰。

direction可能是"_verticalDirection"(垂直滚动方向获取程序)或"_horizontalDirection"(水平滚动方向获取程序)。
在里面在调用_getDirection程序获取滚动方向:
代码
var now = this._getScroll()[ scroll ], _scroll = this._lastScroll;
if ( force ) { _scroll[ scroll ] = now; this._index = 0; return 1; }
var old = _scroll[ scroll ]; _scroll[ scroll ] = now;
return now - old;


原理是通过_getScroll获取当前的滚动值跟上一次的滚动值_lastScroll相差的结果来判断。
如果结果是0,说明没有滚动,如果大于0,说明是向后滚动,否则就是向前滚动。
然后记录当前滚动值作为下一次的参考值。
如果是强制执行(force为true),就重置_index属性为0,并返回1,模拟初始向后滚动的情况。
强制执行适合在不能根据方向做优化的情况下使用,例如第一次加载、resize、刷新等。
虽然不能做优化,但保证了加载的准确性。

在_loadStatic中,先用direction获取方向值:
direction = this[ direction ]( force );
if ( !direction ) return;

没有滚动的话就直接返回。

然后根据方向和上一次的索引来历遍加载集合,其中关键的一点是判断元素是否超过加载范围。
这个主要是通过beforeRange和afterRange程序来判断的。
从_loadData的设置可以看出,它们是包装了对应compare判断程序参数的_outofRange程序。
在"_vertical"方向,compare可能是:
_verticalBeforeRange:垂直平方向上判断元素是否超过加载范围的上边;
_verticalAfterRange:垂直方向上判断元素是否超过加载范围的下边。
在"horizontal"方向,compare可能是:
_horizontalBeforeRange:水平方向上判断元素是否超过加载范围的左边;
_horizontalAfterRange:水平方向上判断元素是否超过加载范围的右边。
在_outofRange中,通过compare来判断是否超过范围:
if ( !this._insideRange( elem, mode ) ) {
    middle.push(elem);
    return this[ compare ]( elem._rect );
}


先用_insideRange判断元素是否在加载范围内,不是的话把元素保存到middle,再用compare判断是否超过加载范围。

回到_loadStatic程序,根据方向判断,如果是向后滚动,先根据索引,取出加载范围前面的元素,保存到begin:
begin = elems.slice( 0, i );


这一部分肯定在加载范围外,不需要再历遍,再向后历遍集合:
for ( var len = elems.length ; i < len; i++ ) {
    if ( afterRange( middle, elems[i] ) ) {
        end = elems.slice( i + 1 ); break;
    }
}
i = begin.length + middle.length - 1;


当afterRange判断超过加载范围后面,根据当前索引取出后面的元素,保存到end。
然后修正索引,给下一次使用。

如果是向前滚动,跟前面相反,根据索引取出加载范围后面的元素,保存到end:
end = elems.slice( i + 1 );


再向前历遍集合:
for ( ; i >= 0; i-- ) {
    if ( beforeRange( middle, elems[i] ) ) {
        begin = elems.slice( 0, i ); break;
    }
}
middle.reverse();


当beforeRange判断超过加载范围前面,根据当前索引取出前面的元素,保存到begin。
由于middle在beforeRange里面是用push添加的,但实际上是倒序历遍,所以要reverse一下。
ps:虽然push/reverse可以直接用unshift代替,但元素越多前者的效率会越高。

最后修正一下索引,合并begin、middle和end成为新的加载集合:
this._index = Math.max( 0, i );
this._elems = begin.concat( middle, end );


这样就完成了一次加载,等待下一次了。

这部分有点抽象,不太好表达,有什么疑问的地方欢迎提出。


【cross模式】

cross模式即正交方向加载模式,是指垂直和水平都需要判断的模式。
也就是说,元素需要同时在两个方向的加载范围内才会加载。
按主次方向又分两种模式:"cross-vertical"(垂直正交)和"cross-horizontal"(水平正交)。
前者以垂直方向为主,水平方向为次,后者相反。

在_initStatic程序中,如果使用cross模式,会设置_crossDirection滚动方向获取程序:
this._crossDirection = $$F.bind( this._getCrossDirection, this,
    isVertical ? "_verticalDirection" : "_horizontalDirection",
    isVertical ? "_horizontalDirection" : "_verticalDirection" );


可以看出,这是包装了primary和secondary参数的_getCrossDirection程序。
其中primary是主滚动方向获取程序,secondary是次滚动方向获取程序。
在_getCrossDirection中会根据主辅方向的滚动情况设置正交滚动值:
代码
direction = this[ primary ]();
secondary = this[ secondary ]();
if ( !direction && !secondary ) {
    return 0;
} else if ( !direction ) {
    if ( this._direction ) {
        direction = -this._direction;
    } else {
        force = true;
    }
} else if ( secondary && direction * this._direction >= 0 ) {
    force = true;
}


包括以下几个情况:
1,主次方向都没有滚动的话,直接返回0;
2,主方向没有滚动而次方向有的话,就用上次滚动的反方向,如果没有上一次滚动就执行强制加载;
3,主次方向都有滚动,同时主滚动方向跟上次不是相反的话,就执行强制加载;
4,主次方向都有滚动,同时主滚动方向跟上次相反的话,按一般情况处理;
5,主方向有滚动而次方向没有的话,就是一般的情况,不用特别处理。

利用两个方向要同时判断的性质,在情况2只要从主方向加载范围内的元素找出在次方向也在加载范围内的就行了。
这个可以通过不断取反方向来实现。

情况3和情况4一般发生在刷新或设置了延迟时间比较长的情况。
如果主方向跟上次相同的话,可能会出现索引两边都有需要加载的元素的情况,不能确定方向,所以只能执行强制加载。
ps:如果在主方向的滚动量超过加载范围的话也能做优化,不过判断比较麻烦就不做了。
而如果方向相反的话,需要加载的元素只会出现在索引到加载范围的方向上,按一般情况历遍就行了。

cross模式跟其余两个静态加载模式的最主要区别就在于方向的判断上。
其他部分都差不多的,就不再详细说明了。


使用技巧

【选择模式】

如果加载元素位置固定大小不固定的情况下只能选择"dynamic"动态加载,否则应该优先选择静态加载。
在静态加载中,如果基本上是用于垂直或水平滚动,应该用"vertical"或"horizontal"模式。
两个方向都需要的话,如果主要是垂直滚动的话就用"cross-vertical"模式,否则用"cross-horizontal"模式。

【延迟html渲染】

Lazyload的一个作用就是延迟html渲染。
原理是先保存元素里面的html,当判断元素在加载范围里面时,再加载里面的html。
程序主要是做判断的部分,而如何保存和加载就看各位的想象力了。
以下几种方法个人认为还不错的:
1,ajax法:保存地址,加载时用ajax读取内容插入元素中;
使用恰当的话能有效节省服务器资源,特别是要读数据库的地方,但响应速度受网络影响。
2,textarea法:把html保存到一个textarea中,加载时把value插入元素中;
利用了textarea的特性,第二个实例就是用的这个方法,使用简单,响应速度快。
不过仅仅是html的话,貌似也没什么必要,可以考虑关联一些dom操作之类的。
2,注释法:把html保存到一个注释中,加载时把内容插入元素中;
跟textarea法类似,但效率应该更好,加载时找出nodeType为8的节点,再把nodeValue插入元素中;
但在ie如果用innerHTML添加注释会被自动忽略掉,使用时注意。

以上方法都有一个问题,在不支持js的时候不能平稳退化,谁有更好的方法的话欢迎赐教。

以上方法都有一个问题,在不支持js的时候不能平稳退化,谁有更好的方法的话欢迎赐教。
除此之外,还可以用来延迟js执行,css渲染等,下一篇还会有图片的延迟加载。

【position的bug】

在写第一个实例的窗口模式时,遇到了两个bug:
在ie6/7,overflow为scroll或hidden的元素,其中position为absolute或relative的子孙元素会出现异常。
解决方法:
1.为包含块元素添加属性position:relative。
2.把该元素的position:relative属性去掉,使用默认的static定位,并通过margin-top等属性实现类似的效果。
参考自“IE6 CSS bug”。

还有一个问题是,在ie6,overflow为visible的元素,会被其内容撑开。
解决方法:
在ie6下,本来overflow为visible的元素设为hidden,并把内容position设为relative。
原理请看“IE6 overflow:visible bug”。

还要注意的是,加载元素只能是容器的子元素。


使用说明

实例化时,有必要参数元素集合,可以是元素数组或NodeList集合。

可选参数用来设置系统的默认属性,包括:
属性:    默认值//说明
container: window,//容器
mode:  "dynamic",//模式
threshold: 0,//加载范围阈值
delay:  100,//延时时间
beforeLoad: function(){},//加载前执行
onLoadData: function(){}//显示加载数据

还提供了以下方法:
load:加载程序;
resize:容器大小改变加载程序,其参数说明是否重置元素集合;
delayLoad:延迟的load程序;
delayResize:延迟的resize程序;
isFinish:指明程序是否执行完成;
dispose:销毁程序,其参数说明是否加载所有元素。
分享到:
评论

相关推荐

    图片延迟加载 lazyload

    &lt;img class="lazyload" data-src="path/to/your/image.jpg" alt="延迟加载图片"&gt; ``` 3. **初始化插件**:在文档加载完成后,调用 jQuery.lazyload 的初始化方法,设置必要的选项。 ```javascript $(function...

    懒加载LazyLoad延时加载图片效果特效.rar

    标题"懒加载LazyLoad延时加载图片效果特效.rar"表明这是一个关于使用懒加载技术实现图片延迟加载,并带有特效的实例。.rar文件通常包含压缩的资源,如HTML文件(LazyLoad.htm)和JavaScript文件(CJL.0.1.min.js),...

    jquery.lazyload图片预加载效果 jquery预加载

    jQuery LazyLoad 是一个jQuery插件,它的主要功能是在用户滚动页面到图片所在位置时才开始加载图片,而不是在页面初始加载时就一次性加载所有图片。这种方式大大减少了首次加载页面时的数据量,提高了页面的加载速度...

    jquery lazyload延时加载

    jQuery LazyLoad 实现了图片的延时加载(也称为懒加载),即只有当图片进入用户可视区域时,才会开始加载,这样可以显著减少页面初次加载时的流量,并提高页面加载速度。 **一、jQuery LazyLoad 基本原理** 1. **...

    lazyload异步加载图片

    本文将围绕"lazyload异步加载图片"这一主题,深入探讨其原理、实现方式以及实际应用中的注意事项。 ### 1. 基本原理 懒加载的核心思想是延迟加载,即不一次性加载所有图片,而是当图片进入用户的可视区域时才进行...

    Lazyload图片延迟加载效果.rar

    标题"Lazyload图片延迟加载效果.rar"指的是这个压缩包包含了一个关于实现懒加载效果的实例。这个实例可能包括了HTML、CSS以及JavaScript代码,用于演示如何在网页中应用懒加载。 描述"Lazyload图片延迟加载效果...

    Lazyload 延迟加载效果 -源码.zip

    在这个"Lazyload 源码.zip"压缩包中,可能包含了实现延迟加载功能的JavaScript代码,可能包括核心逻辑、事件监听、图片占位符处理以及与DOM交互的部分。解压密码为"www.cqlsoft.com",解压后你可以查看源代码,了解...

    Lazyload图片延迟加载效果

    总结起来,"Lazyload图片延迟加载效果"是一个关键的前端性能优化技术,通过JavaScript的`Intersection Observer` API或滚动事件来实现,可以有效减少页面初始化时的加载时间,提高网页的响应速度和用户满意度。...

    JQuery LazyLoad 图片懒加载实例

    为了解决这一问题,我们可以使用`jQuery LazyLoad`插件实现图片的延迟加载,即“懒加载”。本文将详细介绍如何利用jQuery LazyLoad来实现图片的懒加载功能,并通过实例进行演示。 ### 1. jQuery LazyLoad 插件简介 ...

    jquery.lazyload 实现图片延迟加载

    **jQuery LazyLoad 插件详解:实现图片延迟加载** 在网页设计中,为了提升用户体验,减少页面初次加载的时间,一种常见的优化策略是采用图片延迟加载(Image Lazy Load)技术。`jQuery LazyLoad` 是一个非常实用的 ...

    JQuery Lazyload加载图片实例

    为了解决这一问题,jQuery 提供了一个插件——jQuery Lazyload,它允许我们实现图片的延迟加载(也称为懒加载),即图片只在用户滚动到可视区域时才开始加载。这种方式可以显著提升网页的加载速度,特别是对于拥有...

    lazyload.js实现图片异步延迟加载

    `lazyload.js`是一个轻量级、高性能的JavaScript库,专门用于实现图片和其他元素的延迟加载。它具有以下特点: - **简单易用**:只需几行代码即可集成到项目中。 - **兼容性好**:支持所有现代浏览器,包括IE9及...

    js lazyload实现网页图片延迟加载特效

    JavaScript 是实现这一功能的主要工具,它允许我们在用户滚动到图片时才加载图片,而不是一次性加载整个页面的所有资源。本文将详细讲解如何使用 JavaScript 实现网页图片的延迟加载特效。 首先,我们需要理解延迟...

    图片延迟加载,lazyload.js使用实例

    `lazyload.js` 是一个实现图片延迟加载的JavaScript库,它的特点是轻量级且易于使用,没有多余的代码,使得开发者能够快速地集成到项目中。在本文中,我们将深入探讨`lazyload.js`的工作原理、使用方法以及如何在...

    网页内容延迟加载LazyLoad

    网页内容延迟加载(LazyLoad)是一种优化网页性能的技术,它主要应用于图片、视频或大量文本内容的加载。这种技术的核心思想是,只加载用户当前视口内的内容,当用户滚动页面,即将进入视口的内容才会被加载。这样...

    js lazyLoad 图片懒加载demo

    描述中提到的"应用lazyload.js 插件实现图片懒加载",意味着这个项目的核心是通过`lazyload.js`来处理网页上的图片加载。在网页加载时,非首屏的图片不会立即下载,而是等待用户滚动到图片所在位置时才开始加载。...

    LazyLoad 图片延迟加载效果示例 LazyLoad 图片延迟加载效果示例,这是由cloudgamer编写的一个封装插件,用它可以完成不少的效果,现在这个是图片延迟效果,也就是说,我们需要显示的图片会先加载,暂时用不上的也就不加载了,不像有些图片特效一样,不管你是否用到图片,都会全部加载完毕,这样固然在使用时不需要加载了,但是却占用了额外的带宽。

    云游戏玩家(cloudgamer)编写的这个LazyLoad插件,旨在实现这一功能,提供了一种简单而灵活的方法来实现图片延迟加载效果。这个插件的核心原理是监听滚动事件,当图片进入可视区域时,动态地请求图片资源并将其插入...

    lazyload.js图片延迟加载(懒加载)插件

    "lazyload.js图片延迟加载(懒加载)插件" 是一个JavaScript库,主要用于优化网页性能,特别是那些包含大量图片的页面。它实现了“懒加载”技术,即图片不会在页面加载时立即加载,而是在用户滚动到图片即将出现在...

    懒加载lazyload

    `lazyload.js` 的工作原理是监听浏览器的滚动事件,当用户滚动到图片所在的可视区域内时,它会替换 `data-src` 为真实的 `src` 属性,从而加载图片。这种方法有效地避免了未被查看到的图片预先加载,从而节省了加载...

Global site tag (gtag.js) - Google Analytics