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的可视滚动组件,还望大家多多支持!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

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