Vue高性能无限滚动组件的思考与实现

这篇文章阐述了如何一步步实现一个高性能无限滚动组件的实现,最终实现的组件已经收录在awesome-vue仓库中,github代码地址在文章末尾。

笔者目前主要使用Vue技术栈,在工作中碰到一个无限滚动组件(infinite-list)的需求,一个带图带描述信息的列表,如下图所示。

图片列表

初始化时候请求一定数量的一组图片,渲染成列表,等列表滚动到底部时候,再向后端请求下一组图片,更新列表。等列表再滚动到底部,重复上述操作,直到没有更多图片。这是一个非常常见简单的组件,作用是实现列表元素的lazyload,无非就是设定一个高度阈值,监听一下滚动事件,当列表滚动到距离底部不足阈值的时候,加载更多元素,没有更多元素的时候,不再触发加载事件。

我相信,单单实现这个lazyload组件,有一两年开发经验的前端一天就能写出来,那么笔者何必专门写篇文章了?对,问题就是标题的高性能。经验多一些的前端一眼就看出来这个组件的问题,虽然初始的时候列表的元素不多,但随着多次的滚动加载,列表的元素就会越来越多,我们知道,DOM元素越多,页面style和layout的时间越长,所以页面的滚动会越来越卡顿,如果页面是嵌在手机的webview中,某些低端android机器分配给webview的内存不大,甚至会造成app的crash。

那么如何解决这个性能问题了?聪明的读者很快就想出了解决的思路。对,既然问题出在DOM元素过多上,那么我们就想办法来控制DOM元素的个数。对此前端业界有一个通用解决方案叫virtualize,虚拟化,就是把整个列表虚拟化,无论你列表元素有多少,我只虚拟化一定数目的元素(大于一屏幕),然后在滚动过程中动态的更新这些元素,这样的话我们页面重新渲染时候进行的style和layout过程的对象元素就是固定的了,时间不会变长。具体的实现方法参考下图。

列表virtualize(1)

这里假设我们的列表总共有100个元素,每个元素固高为size,设置两个变量remain和bench,分别为5和3。remain就是屏幕上能看到的元素个数,取值为Math.ceil(屏幕高度/size)。bench为缓冲区,屏幕看不到,但是实际存在DOM中。也就是说,实际上我们只渲染总共remain + bench8个元素。那么元素的更新怎么实现了?步骤如下:

1.当列表滚动到item8之前,不做任何操作。

2.滚动到8的时候,此时可见区域的元素是4到8,这时更新DOM的8个元素为4到11,也就是说1-8批量更新成了4-11。但是用户可见区域看到的仍然是4到8,只是bench缓冲区的元素添加了9到11三个元素。

3.元素完成了更新,但是滚动条的位置也要完成更新,因为实际上此时可见区域的item4并不是之前列表DOM中的第4个元素了,而是变成了第1个元素,所以滚动条此时位置变回到了列表的起点,这里需要给列表一个padding-top值,设成3倍元素高度size,从而维持滚动条的位置。

业界大多是按照这个思路来实现virtualize滚动组件的,这里贴一个腾讯alloyteam同事的开源组件,大家先体验一下(10000个元素的滚动)。

大多数场景下,这样设计的组件已经完成了需求,笔者中间也是使用了这个组件,嗯,无论多少元素,也不会造成卡顿和crash了,然而,在快速滚动过程中,发现了另外一个问题,就是列表和滚动条的抖动,并且在列表向上滑动的时候尤其明显,那么原因是什么了?回顾一下上面实现更新的三个步骤。列表往下滚动到8的时候,元素从1-8更新成了4-11,如果这时候你往上滚动了?这个时候item3应当出现,但由于item3已经不在DOM中了,所以又会将整个列表元素更新成3-10,并且更新列表的padding-top来维持滚动条位置。那么继续往上滚动了?2又不在,又得更新成2-9。。。频繁的触发整个列表DOM元素的更新以及重置padding-top的值,导致了抖动的出现。我们在chrome开发者模式下的performance栏记录一下上面贴出来的10000元素滚动的情况(用cpu4xdown来模拟手机),会发现在往上滚动的时候,帧数会明显比往下滚动低,如下图所示。

4xdown的performance

那么还有什么办法能解决这个问题吗?抖动的根本原因是每次更新重置了整个列表DOM的元素,使得每个元素在原本列表文档流中的位置都一同发生了改变,例如4-11更新成3-10,对于item4来说,原本在文档流中排在第一位,更新后变成了第二位,也就是视觉上你会看到item4会突然往下移动了一个item的size,是被item3挤下来的,所以为了维持视觉效果,不得不更新下列表的padding-top值,让item3滚出可视区域,维持item4在可视区域第一位的位置。

所以解决这个问题的根本途径就是将整个列表的元素抽出自上而下的文档流,说一下我的解决步骤:

1. 给每个列表元素加上position:absolute来抽出文档流,并且top都设为0,这样所有的元素都重叠在一起了。

2.给每个元素加上transform:translateY(size * item px),size是元素的高度,item是其本应在列表中的索引值。假设size为20px,也就是item1不动,item2往下位移20px,item3往下位移40px,这样各个元素的位置也就按顺序排列好了。

3.等列表元素需要更新时候,例如4-11更新成3-10,我们只需把item11的位移值改变一下,将其从最底部位置移到第一个位置,并且将11更新成3即可。

4.所有元素都抽出了文档流,所以没有原生滚动条了,那么我们如何滚动列表的位置了,业界有一个通用的滚动解决方案,iScroll,滚动条也是模拟出来的,我们前三个步骤就是基于iScroll来实现的。

利用这个方案改进以后,我们完全消除了抖动,并且在列表元素更新时,无需更新整个列表的元素,只需要更新头部或者尾部的一个元素即可,在快速滚动过程中滚动效果更加平滑。这里贴下我实现的最终组件代码github路径,支持loadmore, pulldown刷新等多种功能,欢迎大家使用,提交issue和改进代码,喜欢地话也请给个star。

GitHub - zuolei828/vue-virtual-infinite-scroll

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

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,703评论 1 92
  • 前言 您将在本文当中了解到,往网页中添加数据,从传统的dom操作过渡到数据层操作,实现同一个目标,两种不同的方式....
    itclanCoder阅读 25,699评论 1 12
  • 在生活中,有些家长,因为大意,或者是对孩子关心的少,就会出现各种情况。 因此,家长们应该多注意孩子,要注意以下的物...
    随意写意阅读 121评论 0 0
  • . 01 毕业那年,老爸费了九牛二虎之力将我安排进了一家事业单位,在一个小乡镇上,每天回家的车只有早上和下午两趟。...
    青岚空灵阅读 309评论 4 2
  • 十五年的等待,还好等对了。
    烟涩寒阅读 238评论 0 0