2019-07-22

海量列表渲染问题

相信任何一个前端开发者都遇到过这种问题,我们需要将大量的数据写入列表,然后再将它们渲染到浏览器上。毫无疑问,当数据量不大的时候,我们不需要做什么处理,一个简单的map直接就搞定了。但是,当数据量达到几万,几十万,甚至几百万,这时候如果你在直接使用一个map的话,估计浏览器会直接崩掉。这是为什么,因为列表中一个元素搞不好的就有多个div,那几万个元素就有几十万个div,浏览器一下子解析几十万个div,大几率会直接崩掉。说实话,这个问题一直都是很令人头疼的,因为虽然有解决方法,但是应用场景的不同很有可能会不适用,今天我们主要来讲几个最常见的解决方案。

分页

分页这种解决方法,可以说是能解决所有海量列表渲染的问题。它的原理就是将数据分成一页页的,所以说每一页只展示了少量的数据,不会存在卡顿的问题。所以这个方案在各大网站上都能看到。但是对我个人而言,我不是很喜欢这种方法,首先从交互上来说,用户可能更倾向于往下滑,一直滑到底。还有一点就是,你点击其他页码的时候,上一个页码的数据就无法保持了,当你想再查看上一个页面数据的时候,还得重新点回去,还要继续往下翻到刚才看的位置,我一直觉得这个操作很反人类。但是仍然有很多网站使用这种方式,所以说肯定是有其自身的优势的,大家可以根据自己的需求去选择最适合自己的。

滚动加载

由于上面我们分析了分页的缺点,那么使用滚动加载就更好了吧。其实不然,滚动加载存在着一个致命的问题,当我们一直往下滚动的时候会出现一个问题,那就是列表越来越大,dom越来越多,当滚动到一定程度时,浏览器也会出现卡顿的问题,然后直接崩掉。这其实跟直接渲染差不太多,只是说它是逐渐增大的。所以说,这种方案也会只适用于中小数据的场景下。

可视区加载

由于滚动加载方案的不适用性,所以说就有了另外一个方案。列表虽然很大,但是在我们视觉范围内的元素只有那么几个。所以说,我们只需要渲染在我们视觉范围内的元素。这样,无论列表有多大,对我们造不成任何影响。竟然有了思路,我们就想办法把它实现出来,下面是我的第一种方案:

  • 获取可视区高度,计算开始渲染的位置和结束的位置
  • 给container绑定scroll事件,根据scrollTops随时更新渲染的开始和结束位置
  • 不在可视区范围内的元素统统设置display:none

看起来没有什么问题,但是实现起来的时候就出现了一个特别难搞的问题,进度条的位置无法保持,因为我们给元素设置display:none的话进度条就会回到起点,尝试过各种方法,进度条位置依然无法保持,所以第一个方案失败。
第一种方案最致命的地方就是进度条位置无法保持,我们直接操作container内的元素,会导致container的高度一直是在变化的,所以进度条根本无法控制。鉴于以上原因,我想除了另外一种方案,当我们对元素进行操作的时候不会影响container的高度,以下是我的第二种方案:

  • 加入一个container,获取container的高度,这个高度就是可视区的高度
  • 获取子元素的高度,计算一屏能够容纳下的元素个数
  • 创建一个set-height元素,它的作用是把container的scroll-height撑起来
  • 创建一个list放置生成的元素,list设置为absolute,对它进行操作不会对container造成影响
  • 滚动时的操作:
    a:获取container的scrollTop,计算list的位置,保证list覆盖container的可视区域。
    b: 通过scrollTop计算出当前可视区域应该显示的元素位置,然后根据位置更新渲染结果
    c: 滚动的时候,list会跟着containe一起往上滚动,所以说这个时候根据translateY('往下移动的距离')来调整list的位置,让它永远保持在可视区域,这个移动的位置可以根据scrollTop计算。实践发现这个方案可行,以下是源码片段:
<body>
      <div class="container">
          <div class="list"></div>   //list设置为absolute
          <div class="scroll-height-box"></div> //撑开container的滚动高度
          <div class="back-top"></div> //回顶
      </div>
    <script>
     var array=[];
     var eleH=150; //元素高度
     var eleW=200; //元素宽度
     var count=1250;
     for(var i=0;i<10000;i++){
        array[i]=i
     }
     var box=document.querySelector('.out-box');
     var container=document.querySelector('.container');
     var list=document.querySelector('.list');
     var heightBox=document.querySelector('.scroll-height-box');
     var backBtn=document.querySelector('.back-top');
     backBtn.onclick=function(){
        container.scrollTop=0;
     }
     window.onload=function(e){
        var visibleHeight=container.offsetHeight;
        var visibleCount=Math.floor(visibleHeight/eleH); //可视区域渲染元素的个数
        var rowCount=Math.floor(container.offsetWidth/(eleW+16));
        list.style.height=visibleHeight+'px'; //设置list的高度为可视高度
        heightBox.style.height=(eleH)*count+'px'; //撑开的高度为元素个数乘以高度
        list.innerHTML=renderNode(0,visibleCount*rowCount+7);
        container.addEventListener('scroll',() => {
          if(container.scrollTop>1200){ //回顶操作
             backBtn.style.visibility='visible';
          }else {
            backBtn.style.visibility='hidden'; 
          }
          list.style.transform=`translateY(${container.scrollTop-container.scrollTop%eleH}px)` //调整list的位置保持永远在可视区域
          var startIndex=Math.floor(container.scrollTop/eleH); //滚动时不断调整元素的开始位置
          list.innerHTML='';
          list.innerHTML=renderNode(startIndex*rowCount,startIndex*rowCount+visibleCount*rowCount+7);
        })
     }
   
     function renderNode(startIndex,stopIndex){
         console.log(startIndex,stopIndex)
         var ele='';
          for(var i=startIndex;i<=stopIndex;i++){
           ele+=`<div class="test-box">${array[i]}</div>`
          }
          return ele
     }

大家要注意一点的是,当给元素绑定scroll事件的时候,一定要给该元素设置高度,并且overflow设置为auto或者scroll,其次就是它的子元素的高度一定要比它高,只有这样才能撑开它的滚动高度。这两个条件一个不符合的话,scroll事件就不会被触发。今天就先到这里了,后面我会专门封装一个react的可视滚动组件,还望大家多多支持!!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,790评论 1 92
  • 🔦 React 的列表渲染 key 与 Reconciliation List and Keys - ReactR...
    云之外阅读 13,412评论 2 11
  • 爬虫基础概念 "大数据时代"从何而来? - 企业生产的用户数据:阿里指数,百度指数,微博指数... - 政府公开的...
    凌晨两点半的蝎子莱莱阅读 477评论 0 1
  • 孩子 我希望你理解 我为什么不让你去动物园 去看马戏团 因为被控制的是一个没有灵魂的躯体 我宁愿带你去广袤的非洲 ...
    卓雅十二阅读 135评论 0 0
  • 拉姆·查兰(Ram Charan,1939-至今),男,国籍:美国,是当代最具影响力的管理咨询大师,在哈佛商学...
    产品社阅读 2,414评论 0 3