本系列主要整理前端项目中需要掌握的知识点。本节介绍Vue2中如何使用虚拟列表。
在项目中,有些请求可能一次性返回上千条信息,这是如果使用v-for循环创建dom节点,创建上千条节点将消耗大量的性能,且不利于首屏加载。

以上的问题可以用虚拟列表来解决。虚拟列表的基本思路是,**在展示区只展示n个dom元素,而dom中展示哪部分内容则根据滚动条计算出来。**这样即使后端返回的数据再多,前端加载的dom元素也只有n个,利于首屏加载的同时也不会造成性能浪费。如图所示:

在图中,无论列表项内容到了多少项,DOM节点都只有10个,只不过是在不断更新而已。
思路与代码来自:凉爽爽爽爽爽爽爽爽爽的vue2 与 vue3 虚拟列表实现。
首先定义一个组件,用于虚拟列表的展示。传入的参数包括items(展示信息数组)、size(每条信息所占高度)、shownumber(每个可视区域展示的信息条数)
<List :items="items" :size="60" :shownumber="10"/>
组件内部的盒子组成如下:外层是一个container、展示列表的盒子是list、list中用v-for遍历展示数据的数组showData,其中showData.length=shownumber。如下图所示:

并给container绑定滚动事件,当滚动事件发生后,计算滚动距离,用滚动距离除以每条数据的高度,即可得到被滚动条卷入的数据条数,因此showData数组的起始位置应该是this.start = Math.floor(scrollTop/this.size),终止位置为起始位置加上数组长度this.end = this.start+this.shownumber。
computed: {
// 最终筛选出的要展示的数据
showData () {
return this.items.slice(this.start, this.end)
},
}
methods: {
// 容器的滚动事件
handleScroll () {
// 获取容器顶部滚动的尺寸
const scrollTop = this.$refs.container.scrollTop
// 计算卷去的数据条数,用计算的结果作为获取数据的起始和结束下标
// 起始的下标就是卷去的数据条数,向下取整
this.start = Math.floor(scrollTop / this.size)
// 结束的下标就是起始的下标加上要展示的数据条数
this.end = this.start + this.shownumber
}
}

还需要对list盒子进行定位,否则滚动条滚动后,dom数据虽然发生了变化,但是同时list盒子中需要展示的前几条数据也被滚动条圈进去了,如上图虚线中。所以就需要将container设置为相对定位,list设置为绝对定位,每次start值变了,便重新计算top的值。
<div
class="list"
:style="{ top: listTop }"
>
<script>
export default {
computed: {
// 列表向上滚动时要动态改变 top 值
listTop () {
return this.start * this.size + 'px'
}
}
}
</script>

完整代码如下:
App.vue中
<template>
<div id="app">
<List
:items="items"
:size="60"
:shownumber="10"
/>
div>
template>
<script>
import List from './components/List.vue'
export default {
name: 'App',
components: {
List,
},
computed: {
// 要进行渲染的数据列表
items () {
// 自己模拟一万条数据,将其内容进行填充
return Array(10000).fill('').map((item, index) => ({
id: index,
content: '列表项内容' + index
}))
}
}
}
script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
style>
List.vue中
<template>
<div
class="container"
:style="{ height: containerHeight }"
@scroll="handleScroll"
ref="container"
>
<div
class="list"
:style="{ top: listTop }"
>
<div
v-for="item in showData"
:key="item.id"
:style="{ height: size + 'px' }"
>
{{ item.content }}
div>
<div
class="bar"
:style="{ height: barHeight }"
/>
div>
div>
template>
<script>
export default {
name: 'VircualList',
props: {
// 要渲染的数据
items: {
type: Array,
required: true
},
// 每条数据渲染的节点的高度
size: {
type: Number,
required: true
},
// 每次渲染的 DOM 节点个数
shownumber: {
type: Number,
required: true
}
},
data () {
return {
start: 0, // 要展示的数据的起始下标
end: this.shownumber // 要展示的数据的结束下标
}
},
computed: {
// 最终筛选出的要展示的数据
showData () {
return this.items.slice(this.start, this.end)
},
// 容器的高度
containerHeight () {
return this.size * this.shownumber + 'px'
},
// 撑开容器内容高度的元素的高度
barHeight () {
return this.size * this.items.length + 'px'
},
// 列表向上滚动时要动态改变 top 值
listTop () {
return this.start * this.size + 'px'
}
},
methods: {
// 容器的滚动事件
handleScroll () {
// 获取容器顶部滚动的尺寸
const scrollTop = this.$refs.container.scrollTop
// 计算卷去的数据条数,用计算的结果作为获取数据的起始和结束下标
// 起始的下标就是卷去的数据条数,向下取整
this.start = Math.floor(scrollTop / this.size)
// 结束的下标就是起始的下标加上要展示的数据条数
this.end = this.start + this.shownumber
}
}
}
script>
<style scoped>
.container {
position: relative;
overflow-y: scroll;
background-color: rgb(150, 195, 238);
font-size: 20px;
font-weight: bold;
line-height: 60px;
text-align: center;
}
.list {
position: absolute;
top: 0;
width: 100%;
}
style>
vue-virtual-scroll-list插件可以实现虚拟列表。
首先下载 vue-virtual-scroll-list插件并引入
npm i vue-virtual-scroll-list
import VirtualList from 'vue-virtual-scroll-list';
export default {
data(){
return {
VirtualList,
}
},
}
使用virtualList标签
<div style="height: 650px;"> //指定列表高度
div>
构建新文件,设定列表内展示的结构
<template>
<div>
{{ source.content }}
div>
template>
<script>
export default {
name: 'item-component',
props: {
source: {
type: Object,
default () {return {}}
},
},
}
script>
完整代码如下:
VirtualList.vue中
<template>
<div style="height: 650px;">
<virtual-list
style="height: 100%; overflow-y: auto;"
:data-key="'id'"
:data-sources="items"
:data-component="itemComponent"
:keeps="5"
/>
div>
template>
<script>
import VirtualList from 'vue-virtual-scroll-list';
import itemComponent from './itemComponent';
export default {
data(){
return {
VirtualList,
itemComponent,
items:[],
}
},
methods:{
getData() {
for(let i = 0; i < 10000; i++){
const obj = {id: i, content: `列表项内容${i}`};
this.items.push(obj)
}
}
},
mounted(){
this.getData()
}
}
script>
itemComponent.vue中
<template>
<div>
{{ source.content }}
<hr/>
div>
template>
<script>
export default {
name: 'item-component',
props: {
// index of current item
source: {
type: Object,
default () {
return {}
}
},
},
mounted(){}
}
script>
App.vue中
<template>
<div id="app">
<VirtualList :items="items">VirtualList>
div>
template>
<script>
import VirtualList from './components/VirtualList.vue'
export default {
name: 'App',
components: {
VirtualList
},
computed: {
// 要进行渲染的数据列表
items () {
// 自己模拟一万条数据,将其内容进行填充
return Array(10000).fill('').map((item, index) => ({
id: index,
content: '列表项内容' + index
}))
}
}
}
script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
style>
最终效果:
