/ 昌里大金猪的空间 / 弱小版 ImageLazyLoad

弱小版 ImageLazyLoad

2011-04-01 posted in [frontend]

延迟加载无非就是在需要时才加载资源,节省系统开销与网络带宽,目的是加快浏览速度。实现思路是将image的实际src值保存到一个自定义属性中,然后通过监听scroll事件判断image元素是否在当前可视区域内,如果是则将这个值设为src。在参考一些代码实现后,尝试实现了一个弱小版的图片延迟加载。所谓弱小版就是说扩展性一般、兼容性一般、代码复杂程度一般,只保证基本功能的代码实现,查看完整实现。这套代码目前运用在相册的评论模块中,只支持垂直方向的延迟加载。

如何调用模块?
  1. this.__lazyModule = np.w._$$ImageLazyLoad._$getInstance({  
  2.     delay: 200,  
  3.     threshold: 200,  
  4.     container: this.__pnode,  
  5.     attribute: 'data-lazyload-src'  
  6. }); 

可以传入哪些参数?
  1.     /** 
  2.      * 图片资源延迟加载对象类(目前只支持垂直方向) 
  3.      * @constructor 
  4.      * @class   图片资源延迟加载对象类 
  5.      * @extends P(N.ut)._$$Singleton 
  6.      * @param   {Object}      _options 可选配置参数,已处理的参数如下: 
  7.      *                                 delay            [Number]        - 滚动延时时间,默认为0 
  8.      *                                 threshold        [Number]        - 加载范围阈值,默认为0 
  9.      *                                 failurelimit     [Number]        - 图片未找到时的重试次数,默认为0 
  10.      *                                 container        [HTML|Element]  - 图片所在的容器,默认为window对象 
  11.      *                                 images           [Array]         - 图片队列,默认为容器内所有的image对象 
  12.      *                                 attribute        [String]        - 图片资源地址的自定义属性,默认为data-lazyload-src 
  13.      *                                 onbeforedataload [Function]      - 图片加载前的回调函数,默认为F 
  14.      *                                 onafterdataload  [Function]      - 图片加载后的回调函数,默认为F

绑定哪些事件?
  1.     /** 
  2.      * 初始化相关事件函数 
  3.      * @return {Void} 
  4.      */  
  5.     __pro.__bindEvent = function(){  
  6.         var _function = this.__beginLoad._$bind(this);  
  7.         this._$addEvent('appear', _function);  
  8.         V._$addEvent(window, 'scroll'this.__delayLoad._$bind(this, _function));  
  9.         V._$addEvent(window, 'resize'this.__delayResize._$bind(this, _function));  
  10.     };
这段代码为window对象绑定scroll事件与resize事件。当拖动滚动条或者鼠标滚动一下时,scroll事件会连续触发,所以在此做个delay。在此绑定resize事件目的是浏览器在resize后需要重新计算images的位置。appear为自定义事件,目的是在页面载入时手动触发一次scroll事件。对此有个疑问,页面动态生成节点导致页面滚动会不会触发scroll事件?

如何判断image元素是否在当前可视区域内?
  1.     /** 
  2.      * 元素是否在可视区域 
  3.      * @param {HTML|Element} _element   目标元素 
  4.      * @return {Boolean} 
  5.      */  
  6.     __pro.__isInViewport = function(_element){  
  7.         return !this.__aboveTheTop(_element) && !this.__belowTheBottom(_element);  
  8.     };  
  9.     /** 
  10.      * 判断元素是否位于当前可视区域的上部 
  11.      * @param {HTML|Element} _element   目标元素 
  12.      * @return {Boolean} 
  13.      */  
  14.     __pro.__aboveTheTop = function(_element){  
  15.         var _top = this.__getClient().top;  
  16.         return _top >= this.__offset(_element).top + this.__offset(_element).height + this.__threshold;  
  17.     };  
  18.     /** 
  19.      * 判断元素是否位于当前可视区域的下部 
  20.      * @param {HTML|Element} _element 
  21.      * @return {Boolean} 
  22.      */  
  23.     __pro.__belowTheBottom = function(_element){  
  24.         var _bottom = this.__getClient().bottom;  
  25.         return _bottom <= this.__offset(_element).top + this.__offset(_element).height - this.__threshold;  
  26.     };
在命名上参考了jQuery。当然了,是否在可视区域内,还可以通过判断两个矩形是否相交来实现。

如何加载元素?
  1.     /** 
  2.      * 开始加载元素 
  3.      * @return {Void} 
  4.      */  
  5.     __pro.__beginLoad = function(){  
  6.         for (var i = 0, j = this.__imgs.length, _img; i < j; i++) {  
  7.             _img = this.__imgs[i];  
  8.             if (this.__isInViewport(_img)) {  
  9.                 this.__onLoadData(_img);  
  10.                 _img.loaded = true;  
  11.             }  
  12.             //  todo add failurelimit  
  13.         }  
  14.         /* Remove image from array so it is not looped next time. thanks to jQuery */  
  15.         this.__imgs = U.arr._$filter(this.__imgs, function(_img){return !_img.loaded;});  
  16.     }; 
把加载完毕的元素过滤掉,不再对其进行遍历。

如果不需要模块支持了,如何销毁?
  1.     /** 
  2.      * 销毁操作 
  3.      * @param {Boolean} _load   是否手动销毁 
  4.      * @return {Boolean} 
  5.      */  
  6.     __pro.__destroy = function(_load){  
  7.         clearTimeout(this.__timer);  
  8.         //  是否加载剩余图片元素  
  9.         if (_load && this.__imgs) {  
  10.             U.arr._$forEach(this.__imgs, function(_img){  
  11.                 this.__onLoadData(_img);  
  12.             }._$bind(this));  
  13.             this.__imgs = null;  
  14.         }  
  15.         this.__clearEvent();    //  bug unfixed:scroll事件无法清除  
  16.     }; 
在此可以做一些清理操作,比如清除scroll和resize事件等。

系统如果动态生成了若干新image节点,怎么办?
  1.     /** 
  2.      * 重新获取元素(用于在指定容器中动态生成新节点时) 
  3.      * @return {Void} 
  4.      */  
  5.     __pro._$reset = function(){  
  6.         this.__destroy();  
  7.         this.__pause = false;  
  8.         this.__imgs = this.__container.getElementsByTagName('img');  
  9.         this.__bindEvent();  
  10.         this.__setPlaceHolder();  
  11.         this._$dispatchEvent('appear');  
  12.     };  
  13.     /** 
  14.      * 暂停延迟加载 
  15.      * @return {Void}  
  16.      */  
  17.     __pro._$pauseLoad = function(){  
  18.         this.__pause = true;  
  19.     }; 
评论模块具有一定的交互特性,用户可以发表评论回复评论,一旦这么做了,就必须重新获取一遍相应容器内的image节点(很直接很粗暴么,也没想到什么更好的解决方案)。_$pauseLoad由调用者手动调用,目的是在动态生成节点浏览器重新渲染时暂停图片的延迟加载进程。this.__setPlaceHolder()给image对象一个1px*1px占位用的图片。不给image设定src的话,ie下会很杯具。

时间精力有限,并没有在移动设备上进行过测试,目前只支持pc下的主流浏览器。

参考资料
Finding the size of the browser window
ImagesLazyLoad 图片延迟加载效果
Lazy Load, 延迟加载图片的 jQuery 插件