技术笔记:Lazyload延迟加载效果

简介: 技术笔记:Lazyload延迟加载效果

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(

相关文章
|
6月前
|
监控 JavaScript 前端开发
js延迟加载的方法?
js延迟加载的方法?
|
1月前
|
缓存 JavaScript 前端开发
延迟加载JS的方式
【10月更文挑战第5天】
62 2
|
6月前
|
JavaScript
vue里样式不起作用的方法,可以通过deep穿透的方式
vue里样式不起作用的方法,可以通过deep穿透的方式
422 0
|
5月前
|
JavaScript 前端开发
JS中延迟加载的方式有哪些
JS中延迟加载的方式有哪些
30 0
|
6月前
|
JavaScript 前端开发
如何实现网页的懒加载?图片懒加载的原理
如何实现网页的懒加载?图片懒加载的原理
48 0
|
6月前
|
JavaScript 前端开发 搜索推荐
JavaScript 延迟加载的艺术:按需加载的最佳实践
JavaScript 延迟加载的艺术:按需加载的最佳实践
JavaScript 延迟加载的艺术:按需加载的最佳实践
|
JavaScript 前端开发 API
js延迟加载的方式有哪些
js延迟加载的方式有哪些
88 0
|
前端开发 JavaScript API
Concis组件库封装——LazyLoad懒加载
Concis组件库封装——LazyLoad懒加载组件封装
100 1
Concis组件库封装——LazyLoad懒加载
|
JavaScript 前端开发 UED
延迟加载是什么,怎么使用?
延迟加载是什么,怎么使用?
95 0
|
JavaScript
jquery的懒加载是如何判断当前页面在视野范围内的?底层原理是什么?
jquery的懒加载是如何判断当前页面在视野范围内的?底层原理是什么?
151 0