产品:现在有10w条数据要展示,不分页,放一个表格里OK不 我 :这一条数据100个属性,浏览器hold不住的 产品:那最多几条 我 :500 ??( 空气凝固3s…) 等我看看再回你
1. 为什么要用虚拟列表
假设我们的列表需要展示1w条数据,我们同时将1w条数据渲染到页面中。看看会花费多少时间。可以粗略的统计到,JS的运行时间为38ms
,但渲染完成后的总时间为957ms
。当JS执行栈中的事件全部执行完后,才会触发渲染线程对页面进行渲染。

通过Performance来看从执行到渲染结束,时间花在了哪里:
Event(click)
: 40.84ms
Recalculate Style
: 105.08ms
Layout
: 731.56ms
Update Layer Tree
: 58.87ms
Paint
: 15.32ms
消耗时间最多的两个阶段是
Recalculate Style
: 样式计算,浏览器根据css选择器计算哪些元素应该应用哪些规则,确定每个元素具体的样式
Layout
: 布局,知道元素应用哪些规则之后,浏览器开始计算它要占据的空间大小及其在屏幕的位置
当DOM节点数量越多的时候,每一次滚动的时候,都会消耗大量的时间,内存用来计算布局,最后导致页面卡顿。但是如果我们只考虑能看到的数据,那实际渲染的数据量就会非常的少,减少了大量不必要的重绘。
2. 实现一个虚拟列表
虚拟列表,实际上是在首屏加载的时候,只加载可视区域需要的列表项,当滚动发生时,动态计算拿到可视区域的列表项,并将非可视区域内存在的列表项删除。

创建一个infinite-list-phantom
虚拟div,把它的高度设置成总高度,用来撑高整个div,生成滚动条。
滚动时,设置可视列表infinite-list
的 transform
位移属性,为滚动高度,并更新可视列表数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| <div ref="list" id="infinite-list-container" @scroll="handleScroll($event)"> <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
<div class="infinite-list" :style="{ transform: getTransform }"> <div class="infinite-list-item" v-for="item in visibleData" :key="item.id" :style="{ height: itemSize + 'px', lineHeight: itemSize + 'px' }" >{{ item.value }}</div> </div> </div> <script> new Vue({ el: '#infinite-list-container', data() { this.itemSize = 30; return { listData: [], start: 0, end: 0, screenHeight: 0, startOffset: 0, }; }, computed: { visibleData() { return this.listData.slice(this.start, Math.min(this.end, this.listData.length)) }, visibleCount() { return Math.ceil(this.screenHeight / this.itemSize) }, listHeight() { return this.listData.length * this.itemSize }, getTransform() { return `translate3d(0, ${this.startOffset}px, 0)` } }, methods: { handleScroll() { let scrollTop = this.$refs.list.scrollTop; this.start = Math.floor(scrollTop / this.itemSize); this.end = this.start + this.visibleCount; this.startOffset = scrollTop - (scrollTop % this.itemSize) } }, mounted() { const arr = []; for (let i = 0; i < 20000; i++) { arr.push({ id: i, value: `第${i + 1}条数据内容`}); } this.listData = arr;
setTimeout(() => { this.screenHeight = this.$el.clientHeight; this.start = 0; this.end = this.start + this.visibleCount; }, 0) }, }) </script> <style> #infinite-list-container { height: 500px; overflow: auto; position: relative; -webkit-overflow-scrolling: touch; } .infinite-list-phantom { position: absolute; top: 0; left: 0; right: 0; z-index: -1; } .infinite-list { position: absolute; top: 0; left: 0; right: 0; } .infinite-list-item { border-bottom: 1px solid #999; } </style>
|
参考资料