美文网首页大前端Web前端之路前端开发那些事
无限滚动的优化方案(二):懒加载实现

无限滚动的优化方案(二):懒加载实现

作者: small_a | 来源:发表于2017-03-11 20:37 被阅读876次

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

    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,从而达到图片懒加载的效果。

    总结

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

    相关文章

      网友评论

      • small_a:看一下简书对xss的防范,<img src="" onerror="alert(hello)">

      本文标题:无限滚动的优化方案(二):懒加载实现

      本文链接:https://www.haomeiwen.com/subject/oqrzgttx.html