移动端图片滚动加载-lazyload实现的要点总结

最近在做移动端的营销页面时,遇到了页面有大量图片的情况,于是很自然的想到了要使用图片lazyload,PC端用着jQuery,也有现成的插件。
但是在移动端,基本不用jQuery,于是就试着自己去造一下*。
实现lazyload并不难,我很快就想到以下几个步骤:

  1. 首先HTML中不直接写图片真实URL,而是用一个空图代替,如<img class="lazy" src="data:images/nopic.png" data-original="images/test.jpg">
  2. 监听滚动scroll事件
  3. 判断图片元素是否出现在屏幕中,如果是则替换src为data-original里的真实URL。

在这简单的几个步骤中,也有几个细节要点需要注意:

  1. 监听事件是scroll,需要考虑是否应用函数节流(毕竟移动端,性能问题不能忘)
  2. 在移动端,水平X方向的滚动是很方便的,判断图片是否出现时,需要考虑水平方向。
  3. 滚动一定距离再刷新页面时,页面会有一次滚动,空图占据的大小若比真实图片大,就会出现在这一次判断中本来不会出现的图,替换真实图片重排后,高度减小,顶上来了。空图如果比真实图片小,就会加载还不应该出现的图,lazyload的效果就打折扣了。PC可直接写定px值,移动端需要使用相似比例的空图。

其中上面要点2比较坑。

垂直Y方向直接监听window的scroll即可,但水平X方向的多半是页面内某个元素设定了一定区域并overflow:auto; 偏偏页面内元素的scroll事件是不会冒泡的,事件的bubbles为false,即不可冒泡

默认的页面滚动事件是可冒泡的,由document开始触发,冒泡到window,事件的bubbles为true,即可冒泡,最坑的是获取和设置scrollTop等值时,却是使用 body 或 html 元素,水平有限,不理解为什么会是割裂开的。

对于这个问题,暂时没想到好的解决方案,暂时用写2次的方法解决,如lazyload(window),再lazyload('#container')监听各自的事件

而判断图片是否出现时,刚开始我是使用elem.offsetTop对比scrollTop,后来发现offsetTop是相对最近的定位元素的,很容易就坑了。

一番寻觅后才发现 elem.getBoundingClientRect(),一番查资料后总结了这个方法的一些要点

elem.getBoundingClientRect() 获取 元素相对于浏览器窗口的距离,会受到滚动的影响,实时获取,translate,scale等transform属性也会影响结果
getBoundingClientRect是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分)。
该函数返回一个Object对象,该对象有6个属性:top,lef,right,bottom,width,height;
这里的top、left和css中的理解很相似,width、height是元素自身的宽高,
但是right,bottom和css中的理解有点不一样。right是指元素右边界距窗口最左边的距离,bottom是指元素下边界距窗口最上面的距离。

踩完一些坑后,总算仿照PC端的jQuery插件完成了*,代码如下

function lazyload(options){
var settings = {
selector : 'img.lazy',
container : window,
threshold : 0,
failurelimit : 0,
dataAttribute : "data-original",
};
if(typeof options == 'object'){
for(var key in options){
settings[key] = options[key] || settings[key];
}
}
var tId = null;
var imgsArr = Array.prototype.slice.call(document.querySelectorAll(settings.selector));
function inViewport(elem, threshold){
var o = elem.getBoundingClientRect();
var pageWidth = document.documentElement.clientWidth;
var pageHeight = document.documentElement.clientHeight;
threshold = threshold || pageHeight/5;
return o.left < pageWidth + threshold && o.top < pageHeight + threshold
}
function loadImg(){
clearTimeout(tId);
tId = setTimeout(function(){
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var src = '';
var item = null;
var counter = 0;
if(imgsArr.length > 0){
for (var i = 0; i < imgsArr.length; i++) {
item = imgsArr[i];
if( inViewport(item) ){
src = item.getAttribute(settings.dataAttribute);
item.setAttribute('src', src);
imgsArr.splice(i,1);
i--;
counter = 0;
}else if( ++counter > settings.failurelimit ){
break;
}
};
}else {
settings.container.removeEventListener('scroll',loadImg);
}
}, 100);
}
loadImg();
settings.container.addEventListener('scroll',loadImg);
}

使用时和jQuery的lazyload插件类似,只是没有了$,直接使用函数lazyload();默认配置如下

lazyload({
selector : 'img.lazy', // 选择器
container : window, // 容器
threshold : 0, // 预留间距,即图片距离屏幕还有一定距离就预先加载
failurelimit : 0, // failurelimit,值为数字.lazyload默认在找到第一张不在可见区域里的图片时则不再继续加载,但当HTML容器混乱的时候可能出现可见区域内图片并没加载出来的情况,failurelimit意在加载N张可见区域外的图片,以避免出现这个问题.
dataAttribute : "data-original", // 存放真实URL的属性
});

以上实现还没有测试兼容,纯属交流,欢迎指正

上一篇:引入jQuery


下一篇:Mono+Jexus部署C# MVC的各种坑