Lazyload是通过延迟加载来实现按需加载,达到节省资源,加快浏览速度的目的。
网上也有不少类似的效果,这个Lazyload主要特点是:
支持使用window(窗口)或元素作为容器对象;
对静态(位置大小不变)元素做了大量的优化;
支持垂直、水平或同时两个方向的延迟。
由于内容比较多,下一篇再介绍图片延迟加载效果。
兼容:ie6/7/8, firefox 3.5.5, opera 10.10, safari 4.0.4, chrome 3.0
效果预览
var LazyLoad = function(elems, options) {
//初始化程序
this._initialize(elems, options);
//初始化container
this._initContainer();
//如果没有元素就退出
if ( this.isFinish() ) return;
//初始化模式设置
this._initMode();
//进行第一次触发
this.resize(true);
};
LazyLoad.prototype = {
//初始化程序
_initialize: function(elems, options) {
this._elems = elems;//加载元素集合
this._rect = {};//容器位置参数对象
this._range = {};//加载范围参数对象
this._loadData = null;//加载程序
this._timer = null;//定时器
this._lock = false;//延时锁定
//静态使用属性
this._index = 0;//记录索引
this._direction = 0;//记录方向
this._lastScroll = { "left": 0, "top": 0 };//记录滚动值
this._setElems = function(){};//重置元素集合程序
var opt = this._setOptions(options);
this.delay = opt.delay;
this.threshold = opt.threshold;
this.beforeLoad = opt.beforeLoad;
this._onLoadData = opt.onLoadData;
},
//设置默认属性
_setOptions: function(options) {
this.options = {//默认值
container: window,//容器
mode: "dynamic",//模式
threshold: 0,//加载范围阈值
delay: 100,//延时时间
beforeLoad: function(){},//加载前执行
onLoadData: function(){}//显示加载数据
};
return $$.extend(this.options, options || {});
},
//初始化容器设置
_initContainer: function() {
var container = $$(this.options.container),
isWindow = container == window || container == document
|| !container.tagName || (/^(?:body|html)$/i).test( container.tagName );
if ( isWindow ) {
var doc = document;
container = doc.compatMode == 'CSS1Compat' ? doc.documentElement : doc.body;
}
//定义执行方法
var oThis = this, width = 0, height = 0;
this.load = $$F.bind( this._load, this );
this.resize = $$F.bind( this._resize, this );
this.delayLoad = function() { oThis._delay( oThis.load ); };
this.delayResize = function(){//防止重复触发bug
var clientWidth = container.clientWidth,
clientHeight = container.clientHeight;
if( clientWidth != width || clientHeight != height ) {
width = clientWidth; height = clientHeight;
oThis._delay( oThis.resize );
}
};
//记录绑定元素方便移除
this._binder = isWindow ? window : container;
//绑定事件
$$E.addEvent( this._binder, "scroll", this.delayLoad );
isWindow && $$E.addEvent( this._binder, "resize", this.delayResize );
//获取容器位置参数函数
this._getContainerRect = isWindow && ( "innerHeight" in window )
? function(){ return {
"left": 0, "right": window.innerWidth,
"top": 0, "bottom":window.innerHeight
}}
: function(){ return oThis._getRect(container); } ;
//设置获取scroll值函数
this._getScroll = isWindow
? function() { return {
"left": $$D.getScrollLeft(), "top": $$D.getScrollTop()
}}
: function() { return {
"left": container.scrollLeft, "top": container.scrollTop
}};
},
//初始化模式设置
_initMode: function() {
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;
}
},
//初始化静态加载设置
_initStatic: function(mode, direction) {
//设置模式
var isVertical = direction == "vertical";
if ( mode == "cross" ) {
this._crossDirection = $$F.bind( this._getCrossDirection, this,
isVertical ? "_verticalDirection" : "_horizontalDirection",
isVertical ? "_horizontalDirection" : "_verticalDirection" );
}
//设置元素
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.//代码效果参考:http://hnjlyzjd.com/hw/wz_24809.html
_elems = $$A.map( this._elems, getRect, this ).sort( sortFunction );};
//设置加载函数
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" ) );
},
//延时程序
_delay: function(run) {
clearTimeout(this._timer);
if ( this.isFinish() ) return;
var oThis = this, delay = this.delay;
if ( this._lock ) {//防止连续触发
this._timer = setTimeout( function(){ oThis._delay(run); }, delay );
} else {
this._lock = true; run();
setTimeout( function(){ oThis._lock = false; }, delay );
}
},
//重置范围参数并加载数据
_resize: function(change) {
if ( this.isFinish() ) return;
this._rect = this._getContainerRect();
//位置改变的话需要重置元素位置
if ( change ) { this._setElems(); }
this._load(true);
},
//加载程序
_load: function(force) {
if ( this.isFinish() ) return;
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
}
//加载数据
this.beforeLoad();
this._loadData(force);
},
//动态加载程序
_loadDynamic: function() {
this._elems = $$A.filter( this._elems, function( elem ) {
return !this._insideRange( elem );
}, this );
},
//静态加载程序
_loadStatic: function(direction, beforeRange, afterRange, force) {
//获取方向
direction = this【 direction 】( force );
if ( !direction ) return;
//根据方向历遍图片对象
var elems = this._elems, i = this._index,
begin = 【】, middle = 【】, end = 【】;
if ( direction > 0 ) {//向后滚动
begin = elems.slice( 0, i );
for ( var len = elems.length ; i = 0; i-- ) {
if ( beforeRange( middle, elems【i】 ) ) {
begin = elems.slice( 0, i ); break;
}
}
middle.reverse();
}
this._index = Math.max( 0, i );
this._elems = begin.concat( middle, end );
},
//垂直和水平滚动方向获取程序
_verticalDirection: function(force) {
return this._getDirection( force, "top" );
},
_horizontalDirection: function(force) {
return this._getDirection( force, "left" );
},
//滚动方向获取程序
_getDirection: function(force, scroll) {
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;
},
//cross滚动方向获取程序
_getCrossDirection: function(primary, secondary, force) {
var direction;
if ( !force ) {
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;//同时滚动并且方向跟上一次滚动相同
}
}
if ( force ) {
this._lastScroll = this._getScroll(); this._index = 0; direction = 1;
}
return ( this._direction = direction );
},
//判断是否加载范围内
_insideRange: function(elem, mode) {
var range = this._range, rect = elem._rect || this._getRect(elem),
insideH = rect.right >= range.left && rect.left = range.top && rect.top <= range.bottom,
inside = {
"horizontal": insideH,
"vertical": insideV,
"cross": insideH && insideV
}【 mode || "cross" 】;
//在加载范围内加载数据
if ( inside ) { this._onLoadData(elem); }
return inside;
},
//判断是否超过加载范围
_outofRange: function(mode, compare, middle, elem) {
if ( !this._insideRange( elem, mode ) ) {
middle.push(elem);
return this【 compare 】( elem._rect );
}
},
_horizontalBeforeRange: function(rect) { return rect.right this._range.right; },
_verticalBeforeRange: function(rect) { return rect.bottom this._range.bottom; },
//获取位置参数
_getRect: function(node) {
var n = node, left = 0, top = 0;
while (n) { left += n.offsetLeft; top += n.offsetTop; n = n.offsetParent; };
return {
"left": left, "right": left + node.offsetWidth,
"top": top, "bottom": top + node.offsetHeight
};
},
//是否完成加载
isFinish: function() {
if ( !this._elems || !this._elems.length ) {
this.dispose(); return true;
} else {
return false;
}
},
//销毁程序
dispose: function(load) {
clearTimeout(this._timer);
if ( this._elems || this._binder ) {
//加载全部元素
if ( load && this._elems ) {
$$A.forEach( this._elems, this._onLoadData, this );
}
//清除关联
$$E.removeEvent( this._binder, "scroll", this.delayLoad );
$$E.removeEvent( this._binder, "resize", this.delayResize );
this._elems = this._binder = null;
}
}
}
.lazy{ width:600px;overflow:scroll;border:1px solid #ccc;}
.lazy table{border-collapse:collapse;}
.lazy td{border:1px solid #ccc;text-align:center;padding:20px;background:#FFF;}
#idLazyContainer{height:250px;*position:relative;}
#idLazyContainer table{position:relative;}
#idLazyContainer div{width:140px;height:100px;line-height:100px;position:relative;}
模式: 垂直正交方向 水平正交方向 垂直方向 水平方向 动态加载 阈值:
第 1 个加载
function Lazy(lazycontainer, random){
var table = $$("idLazy"), totalX = 10, totalY = 10, i = 0,
container = $$("idLazyContainer"), color = 【1,1,1】;
lazycontainer = lazycontainer || container;
while(table.firstChild){ table.removeChild(table.firstChild); }
for ( var n = 0; n < totalY; n++ ) {
var row = table.appendChild(document.createElement("tr"));
for ( var m = 0; m < totalX; m++ ) {
if ( random ) {
var css = "style='" + $$A.map(【"left", "top"】, function(style){
return style + ":" + (Math.floor(Math.random() 100) - 50) + "px;";
}).join("") + "'";
}
row.appendChild(document.createElement("td")).innerHTML = "";
}
};
setTimeout(function(){//ie6渲染bug
var lazy = new LazyLoad( container.getElementsByTagName("div"), {
container: lazycontainer || container,
mode: $$("idMode").value,
threshold: $$("idRange").value | 0,
beforeLoad: function() {
color = $$A.map(color, function(){ return Math.floor((Math.random() * 255)); });
},
onLoadData: function(elem) {
elem.innerHTML = "第 " + ++i + " 个加载";
elem.style.backgroundColor = "rgb(" + color.join(",") + ")";
elem.style.color = "rgb(" + $$A.map(color, function(x){ return 255 - x; } ).join(",") + ")";
}
});
$$("idReset").onclick = function(){
lazy.dispose(); Lazy(lazycontainer, random);
}
var style = container.style;
$$("idWindow").onclick = function(){
if( this.value == "窗口模式" ){
lazy.dispose();
if ( $$B.ie6 ) {container.scrollTop = container.scrollLeft = 0;
style.overflow = "hidden"; style.position = "static";
} else {
style.overflow = "visible";
}
Lazy(window, random);
this.value = "容器模式"
}else{
lazy.dispose();
if ( $$B.ie6 ) {
style.position = "relative";
}
style.overflow = "scroll";
Lazy(container, random);
this.value = "窗口模式";
}
}
$$("idRandom").onclick = function(){
if( this.value == "随机位置" ){
lazy.dispose(); Lazy(lazycontainer, true);
this.value = "固定位置"
}else{
lazy.dispose(); Lazy(lazycontainer, false);
this.value = "随机位置";
}
}
},0);
}
Lazy();
#idLazyContainer2{height:200px;}
#idLazyContainer2 div{width:140px;height:100px;line-height:25px;}
#idLazyContainer2 textarea{display:none;}
利用textarea加载数据:
图片滑动切换效果
图片切割效果
图片变换效果(ie only)
仿LightBox内容显示效果
图片滑动展示效果
仿163网盘无刷新多文件上传系统
拖放效果
滑动条效果
Table排序
图片切割系统
拖拉缩放效果
Tween算法及缓动效果
颜色梯度和渐变效果
blog式日历控件
渐变效果
Table行定位效果
多级联动浮动菜单
浮动定位提示效果
简便无刷新文件上传系统
图片上传预览效果
var container = $$("idLazyContainer2"), lazyTable = container.innerHTML;
lazyTable = lazyTable.replace(//ig, '');
function Lazy2(){
container.innerHTML = lazyTable;
var elems = $$A.map( container.getElementsByTagName("textarea"), function(area){ return area.parentNode; }),
lazy = new LazyLoad( elems, {
container: container, mode: "cross",
onLoadData: function(elem) {
elem.innerHTML = elem.getElementsByTagName("textarea")【0】.value;
}
});
$$("idReset2").onclick = function(){ lazy.dispose(); Lazy2(); }
}
Lazy2()
程序说明
【基本原理】
首先要有一个容器对象,容器里面是_elems加载元素集合。
用隐藏或替换等方法,停止元素加载内容。
然后历遍集合元素,当元素在加载范围内,再进行加载。
加载范围一般是容器的视框范围,即浏览者的视觉范围内。
当容器滚动或大小改变时,再重新历遍元素判断。
如此重复,直到所有元素都加载后就完成。
【容器对象】
程序一开始先用_initContainer程序初始化容器对象。
先判断是用window(窗口)还是一般元素作为容器对象:
var doc = document,
isWindow = container == window || container == doc
|| !container.tagName || (/^(?:body|html)$/i).test( container.tagName );
如果是window,再根据文档渲染模式选择对应的文档对象:
if ( isWindow ) {
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),应手动执行load或resize方法。
当容器大小改变(resize)时,还需要先执行_getContainerRect程序获取视框范围。
要获取视框范围,一般元素可以通过_getRect方位参数获取程序来获取。
但如果容器是window就麻烦一点,测试以下代码:
代码
<!doctype html
[/span>style
[/span>body
[/span>div style="border:1px solid #000;height:2000px;"
[/span>script
alert(document.documentElement.offsetHeight)
在ie会得到想要的结果,但其他浏览器得到的是文档本身的高度。
所以在_getContainerRect程序中,其他浏览器要用innerWidth/innerHeight来获取:
代码
this._getContainerRect = isWindow && ( "innerHeight</span>" 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(