什么是虚拟列表
虚拟列表是指对列表的 可视区域
进行渲染,对 非可见区域
不渲染或部分渲染,从而极大提高渲染性能的一种技术。
为什么要用虚拟列表
有时我们会遇到一些业务场景,要展示的列表很长,且不能使用分页的方式,如果一次性把数据全部渲染到页面,浏览器将变得非常卡顿,因为渲染 dom
需要耗费大量时间。虚拟列表
就是对长列表的一种优化方式,通过只渲染可视区域数据,大大提高渲染性能。
如何使用虚拟列表
目前虚拟列表已经有很多知名的库,如 vue-virtual-scroller、vue-virtual-scroll-list、react-virtualized 等, 下面就给大家介绍一下 vue-virtual-scroller
这个优秀库的使用方法,然后再带大家实现一个简版的虚拟列表。准备好了吗,开干!
安装
npm install --save vue-virtual-scroller
RecycleScroller组件
适用于列表每一项高度确定的情况,高度可设置成相同,也可单独配置每一项高度。
src/components/virtualRecycleScroller.vue
<template>
<RecycleScroller
class="scroller"
:items="items"
:item-size="50"
key-field="id"
v-slot="{ item }"
>
<div class="desc">
{{ item.label }}
</div>
</RecycleScroller>
<!-- items: 需要渲染的列表,itemSize: 列表项的高度,keyField: 列表循环的key值 -->
</template>
<script>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
export default {
components: {
RecycleScroller
},
props: {
items: Array
}
}
</script>
<style scoped>
.scroller {
height: 100%;
}
.desc {
height: 50px;
line-height: 50px;
text-align: center;
box-sizing: border-box;
border: 1px solid #ccc;
}
</style>
src/App.vue
<template>
<div class="container">
<virtual-recycle-scroller :items="list" />
</div>
</template>
<script>
import virtualRecycleScroller from '@/components/virtualRecycleScroller'
// 模拟一个长列表
const list = []
for(let i=0; i<10000; i++) {
list.push({
id: i,
label: `virtual-list ${i}`
})
}
export default {
components: {
virtualRecycleScroller
},
data() {
return {
list: list
}
}
}
</script>
<style scoped>
.container {
height: 300px;
border: 1px solid #ccc;
}
</style>
效果如下:
一万条数据的列表瞬间就渲染出来了,滚动也丝滑无比,是不是很nice!
DynamicScroller组件
适用于列表每一项高度不确定的情况。
src/components/virtualDynamicScroller.vue
<template>
<DynamicScroller class="scroller" :items="items" :min-item-size="50">
<template v-slot="{ item, index, active }">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.label]"
:data-index="index"
>
<div class="desc">{{ item.label }}</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
<!-- minItemSize: 列表项初次渲染使用的最小高度-->
<!-- active: 保持视图,防止不必要的重新计算 -->
<!-- sizeDependencies: 影响高度的值,如果发生变化,则重新计算 -->
</template>
<script>
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
export default {
components: {
DynamicScroller,
DynamicScrollerItem
},
props: {
items: Array
}
}
</script>
<style scoped>
.scroller {
height: 100%;
}
.desc {
padding: 12px;
text-align: center;
border: 1px solid #ccc;
}
</style>
src/App.vue
<template>
<div class="container">
<virtual-dynamic-scroller :items="list" />
</div>
</template>
<script>
import virtualDynamicScroller from '@/components/virtualDynamicScroller.vue'
// 模拟一个长列表
const list = []
for(let i=0; i<10000; i++) {
list.push({
id: i,
label: `virtual-scroller ${i}`
})
}
// 模拟一个内容不同的列表项
list[2].label = `virtual-scroller 2 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞
前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞
前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 前端阿飞 `
export default {
components: {
virtualDynamicScroller
},
data() {
return {
list: list
}
}
}
</script>
<style scoped>
.container {
height: 300px;
border: 1px solid #ccc;
}
</style>
效果如下:
可以看到列表项的高度是随内容的变化而变化,依旧是丝滑无比!
虚拟列表原理
初次听到 “虚拟列表” 这个名词感觉非常的高大上,其实弄清楚它的原理之后,你会发现它非常的简单。话不多说,先上图:
- 可视区容器:可以看作是在最底层,容纳所有元素的一个盒子。
- 可滚动区域:可以看作是中间层,假设有 10000 条数据,每个列表项的高度是 50,那么可滚动的区域的高度就是 10000 * 50。这一层的元素是不可见的,目的是产生和真实列表一模一样的滚动条。
- 可视区列表:可以看作是在最上层,展示当前处理后的数据,高度和可视区容器相同。可视区列表的位置是动态变化的,为了使其始终出现在可视区域。
理解以上概念之后,我们再看看当滚动条滚动时,我们需要做什么:
- 根据滚动距离和
item
高度,计算出当前需要展示的列表的startIndex
- 根据
startIndex
和 可视区高度,计算出当前需要展示的列表的endIndex
- 根据
startIndex
和endIndex
截取相应的列表数据,赋值给可视区列表,并渲染在页面上 - 根据滚动距离和
item
高度,计算出可视区列表的偏移距离startOffset
,并设置在列表上
原理就是这些,不知道大家有木有听明白。俗话说 “书读百遍,其义自现” ,但我更相信 “实践出真知” ,接下来我们就自己动手实现一个虚拟列表吧!
手写一个简版的虚拟列表
src/components/myVirtualScroller.vue
<template>
<!-- 最底层的可视区容器 -->
<div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
<!-- 中间的可滚动区域,z-index=-1,高度和真实列表相同,目的是出现相同的滚动条 -->
<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' }"
>
{{ item.label }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'MyVirtualList',
props: {
//列表数据
items: {
type: Array,
default: () => []
},
//列表项高度
itemSize: {
type: Number,
default: 50
}
},
computed: {
//列表总高度
listHeight() {
return this.items.length * this.itemSize
},
//可视区列表的项数
visibleCount() {
return Math.ceil(this.screenHeight / this.itemSize)
},
//可视区列表偏移距离对应的样式
getTransform() {
return `translate3d(0,${this.startOffset}px,0)`
},
//获取可视区列表数据
visibleData() {
return this.items.slice(this.start, Math.min(this.end, this.items.length))
}
},
mounted() {
this.screenHeight = this.$refs.list.clientHeight
this.start = 0
this.end = this.start + this.visibleCount
},
data() {
return {
screenHeight: 0, //可视区域高度
startOffset: 0, //偏移距离
start: 0, //起始索引
end: 0 //结束索引
}
},
methods: {
scrollEvent() {
//当前滚动位置
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)
}
}
}
</script>
<style scoped>
.infinite-list-container {
height: 100%;
overflow: auto;
position: relative;
}
.infinite-list-phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.infinite-list {
left: 0;
right: 0;
top: 0;
position: absolute;
}
.infinite-list-item {
line-height: 50px;
text-align: center;
color: #555;
border: 1px solid #ccc;
box-sizing: border-box;
}
</style>
src/App.vue
<template>
<div class="container">
<my-virtual-scroller :items="list" />
</div>
</template>
<script>
import myVirtualScroller from '@/components/myVirtualScroller'
// 模拟一个长列表
const list = []
for(let i=0; i<10000; i++) {
list.push({
id: i,
label: `virtual-list ${i}`
})
}
export default {
components: {
myVirtualScroller
},
data() {
return {
list: list
}
}
}
</script>
<style scoped>
.container {
height: 300px;
border: 1px solid #ccc;
}
</style>
效果和上面基本是一样:
打开控制台,可以看到页面始终只渲染了6条左右的数据:
以上就是简版的虚拟列表,大家可以自己动手试一试。如果要做的更加完善,还需考虑缓冲区域、列表项高度自适应等,有兴趣的同学可以自己研究一哈。
今天的分享就到这里,相信下次遇到这种场景你应该知道怎么处理了。你的支持就是我最大的动力,如果文章对你有所帮助,不要忘了点个免费的赞呦~😉
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。