之前对预加载的实现方案做了介绍,这一篇文章主要是我对图片的懒加载的实现的一个总结。主要包括:

  1. 视区检测
  2. 图片懒加载及延迟显示

实例简介

之前一直对单页应用有兴趣,所以自己写了一个前端路由,相关的文章见这里,这个单页应用采取hash的方式实现路由。最终的实例页面见这里。仓库在这里是一个经典的单页应用。要做优化的就是主页的信息滚动。这些信息通过ajax从服务器端获取,这里为了方便,服务器端会一直返回数据,哪怕是重复的。页面如下:

Paste_Image.png

懒加载

可以看到,页面中,每一条消息都有一个图片,这个时候,如果在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(){
  //主函数
  //实现初始化
  //滚动事件的绑定等
}

获取元素相对页面顶部的高度

这个函数其实不难,主要涉及到目标元素下面几个属性:

  1. node.offsetTop:相对其父元素的位置
  2. 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 //定期清理,加载之后的图片信息进行清除,降低内存使用 
      })

视区的检测

图片是否落在用户视区,需要用到以下高度:

  1. height1:document.body.scrollTop:浏览器滚动的高度
  2. height2:document.body.clientHeight:可视区域的高度
  3. 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,从而达到图片懒加载的效果。

总结

这次无限滚动,我实现了两种方案:预加载与图片懒加载,配合消抖和节流以及缓存,能够很好的提升页面性能。希望面试的时候能用上吧。