之前对预加载的实现方案做了介绍,这一篇文章主要是我对图片的懒加载的实现的一个总结。主要包括:
- 视区检测
- 图片懒加载及延迟显示
实例简介
之前一直对单页应用有兴趣,所以自己写了一个前端路由,相关的文章见这里,这个单页应用采取hash的方式实现路由。最终的实例页面见这里。仓库在这里是一个经典的单页应用。要做优化的就是主页的信息滚动。这些信息通过ajax从服务器端获取,这里为了方便,服务器端会一直返回数据,哪怕是重复的。页面如下:
懒加载
可以看到,页面中,每一条消息都有一个图片,这个时候,如果在dom刚刚建立好,就对所有的图片进行加载,此时,占用过多的下载通道(我的是每次显示10条消息,接近页面底部时会预加载),会导致后面的信息加载速度变慢,用户体验不好。而图片懒加载是指:图片进入用户视野才会进行加载,而不是在dom树一构建好就进行加载。 道理很简单,但是我在实现的过程中还是碰到了一些问题,下面就是我的实现方案。
懒加载实现方案
总体变量以及函数定义
//记录图片的序号
let num = 0;
//记录是否正在获取数据,保证请求只做一次
let state = true;
//记录图片数据,index,src,height三个关键元素
var img_data =[];
//记录表单的距离页面顶端的距离
var list_height = 0;
function getH(obj) {
//获得对象距离页面顶端的距离
}
function lazy_load(){
//图片懒加载的实现函数
}
function getInfo(){
//从服务器端获取商家发布的新信息
//并向图片数据中存放图片信息
}
function main(){
//主函数
//实现初始化
//滚动事件的绑定等
}
获取元素相对页面顶部的高度
这个函数其实不难,主要涉及到目标元素下面几个属性:
- node.offsetTop:相对其父元素的位置
- node.offsetParent: 元素的父元素
所以,要获取元素相对页面顶部的高度,其实只需要进行递归或者迭代就能实现,这里采用迭代实现:
function getH(obj) {
var h = 0;
while (obj) {
h += obj.offsetTop;
obj = obj.offsetParent;
}
return h;
}
数据的缓存
程序中,通过Ajax从服务器获取数据,每次最多获取10条,在dom中,img标签最开始并不指定src,src存储在ajax获取到的信息中,我将其存入:img_data中,与它一同存入的,还有该图片的高度height,第几条信息index。
这里的height,可以采用上面的迭代得到,但是每次迭代对资源损耗比较大,事实上也是没有必要的,因为每条信息是固定的高度,所以根据其是第几条信息,再获取一个list相对页面顶部的高度,就能得到图片相对页面顶部的高度。我这里每个图片(100px)算上间隙(40px)就是140px,只需要获取整个列表相对顶部的高度,就能得到每个图片相对页面顶部的距离。
程序中大概像这样子:
img_data.push({
index:(num),
height:list_height+(140)*(num),
src:data.src,
loaded:false //定期清理,加载之后的图片信息进行清除,降低内存使用
})
视区的检测
图片是否落在用户视区,需要用到以下高度:
- height1:document.body.scrollTop:浏览器滚动的高度
- height2:document.body.clientHeight:可视区域的高度
- height3:node.height:也就是之前获取到的元素相对页面顶部的高度(并不是相对可视区域的顶部)
当height3>heihgt1且height3<height2+height1的时候,可以认为这个元素是出现在用户视区的,从而将img_data的src赋值给这个块的img标签,当图片加载好之后,opacity配合transition实现动态的浮现(据说,人感觉这样加载的速度更快)。这一块大致的代码如下:
function lazy_load(){
var height1 = document.body.scrollTop+document.body.clientHeight;
img_data.forEach(function(item){
if(!item.loaded && item.height>document.body.scrollTop-100 && item.height < height1){
var img = document.querySelector("img[img-index='"+item.index+"']");
//选择该图片
if(img){
img.src = item.src;
item.loaded = true; //下面对img_data进行filter的函数,减少内存消耗
img.onload = function(){
img.style.opacity = 1;//配合transition可以实现一个渐入的效果
}
img.onerror = function(){
img.style.opacity = 1;
img.src = '/failed.jpg';//加载失败,
}
}
}
img_data = img_data.filter(function(item){
return !item.loaded;
})
}
滚动函数的绑定
直接将上述函数和window.onscroll进行绑定是不太理想的,因为滚动函数的触发频率很高,而视区的检测如果每次滚动都进行检测,那么,一方面造成性能上的损失,一方面,似乎所有的图片都能被检测到出现在了视区,从而导致所有的图片都会被加载,并没有起到懒加载的作用。所以在这里,我使用了函数消抖,原理也不难,网上的实现很多,这里给出我的实现:
method.debounce = function(func,delay){
var timer;
return function(){
var args = arguments;
var context = this;
clearTimeout(timer);
timer = setTimeout(function(){
func.apply(this,args);
},delay);
}
}
和上述lazy_load结合,进行绑定,代码如下:
var lazy_event = method.debounce(lazy_load,500);//此处500ms可以适当缩小
method.addevent(window,'scroll',lazy_event);
和消抖函数结合之后,用户的滚动不会触发lazy_load,只有当用户停止滚动才会执行lazy_load,从而达到图片懒加载的效果。
总结
这次无限滚动,我实现了两种方案:预加载与图片懒加载,配合消抖和节流以及缓存,能够很好的提升页面性能。希望面试的时候能用上吧。