正常渲染长列表时是全部渲染出来的,但是当列表很长的时候,会造成渲染时间慢,卡顿等问题;那么就可以使用虚拟列表来解决;虚拟列表:只渲染可视化区域内的列表;
正常的渲染是这样的:
换个思路:可视化盒子里面定义两个div,第一个div的长度是真实列表的长度,用于在可视化盒子中撑开滚动条;第二个盒子用于展示列表,该列表的高度取决于可视化盒子的高度,计算出可渲染的列表数量;第二个div是通过子绝父相定位的,当滚动条滚动时,第二个div也会跟着滚动,通过定义top属性,来让第二个div始终出现在可视化盒子中;滚动的时候添加滚动事件,用于获取可渲染列表的起始索引,然后截取全部列表作为可渲染列表;
绿色的盒子不作为全部列表展示的盒子,仅作为和全部列表的高度一样的盒子撑开滚动条,真正展示可渲染的盒子是蓝色盒子;
下面定义下盒子:
<div class="box" id="box">
<!-- 撑开滚动条盒子 -->
<div :style="{ height: textHeight + 'px' }" id="textBox"></div>
<!-- 可渲染列表盒子 -->
<div class="visibelBox" :style="{ top: getOffset }">
<div class="item" id="item" v-for="item in visibelList" :key="item">
{{ item + 1 }}
</div>
</div>
</div>
先定义好所有需要的变量:
item的高度:itemHeight:50
全部list:list:[1,2,3,4,5,....,100]
可视化盒子box的高度:boxHeight = document.getElementById('box').clientHeight
可渲染列表visibelList:visibelList:[]
,通过计算获得visibelList = list.splice(startIndex, endIndex)
可渲染区域盒子可展示的条数:visibelNum = Math.ceil(boxHeight / itemHeight)
截取的开始索引:startIndex:0
,随着滚动条滚动重新计算 scrollTop = Math.floor(scrollTop / itemHeight)
截取的结束索引:endIndex = startIndex + visibelNum
滚动条滚动的距离:scrollTop = document.getElementById('box').scrollTop
可渲染列表盒子的偏移量:getOffset = scrollTop - (scrollTop % itemHeight)
,这个很关键,当刚好滚动item的顶部时,偏移量就等于scrollTop ,当不是滚动到item的顶部时,由于还没有重新计算visibelList,所以要相对于box来说是往上偏移一点,当刚好滚动到item的顶部的时候,会重新计算visibelList,这个时候相对于box的偏移量是0,所以直接等于scrollTop
根据以上的变量可以计算出:visibelList渲染列表和getOffset 获取偏移量,即可实现虚拟列表
下面是完整代码:
<template>
<div>
<el-row>
<el-card class="box-card">
<h3>虚拟列表</h3>
<div class="box" id="box">
<!-- 撑开滚动条盒子 -->
<div :style="{ height: textHeight + 'px' }" id="textBox"></div>
<!-- 内容盒子 -->
<div class="visibelBox" :style="{ top: getOffset }">
<div class="item" id="item" v-for="item in visibelList" :key="item">
{{ item + 1 }}
</div>
</div>
</div>
</el-card>
</el-row>
</div>
</template>
<script>
export default {
data() {
return {
textHeight: null,
visibelList: [],
startIndex: null,
endIndex: null,
visibelNum: null,
scrollHeight: null,
scrollTop: null
}
},
computed: {
// 生成列表数据
list() {
let arr = []
for (let i = 0; i < 100; i++) {
arr.push(i)
}
return arr
},
// 设置偏移量
getOffset() {
return (this.scrollTop - (this.scrollTop % 50)) + 'px'
}
},
mounted() {
this.getTextHeight()
this.getVisibelNum()
this.getIndex()
this.onscroll()
},
methods: {
// 全部内容撑开的高度
getTextHeight() {
this.textHeight = 50 * this.list.length
console.log("jkjdsfj", this.textHeight);
},
// 获取最多能展示的条数
getVisibelNum() {
let box = document.getElementById('box')
this.visibelNum = Math.ceil(box.clientHeight / 50)
},
// 获取可见的开始索引和结束索引
getIndex() {
this.startIndex = 0
this.endIndex = this.startIndex + this.visibelNum
this.visibelList = this.list.slice(this.startIndex, this.endIndex)
},
// 侦听滚动
onscroll() {
let box = document.getElementById('box')
box.addEventListener('scroll', (e) => {
this.scrollTop = box.scrollTop
this.startIndex = Math.floor(box.scrollTop / 50)
this.endIndex = this.startIndex + this.visibelNum
this.visibelList = this.list.slice(this.startIndex, this.endIndex)
})
}
}
}
</script>
<style lang="scss" scoped>
* {
box-sizing: border-box;
}
.box {
margin: 0 auto;
width: 600px;
height: 700px;
border: 1px solid #ccc;
overflow: auto;
position: relative;
.visibelBox {
position: absolute;
top: 0;
width: 100%;
.item {
text-align: center;
height: 50px;
line-height: 50px;
border: 1px solid greenyellow;
}
}
}
</style>
还有一个点是需要注意的:虽然box用了相对定位,visibelBox用了绝对定位,按道理来说visibelBox是相对于box定位的,但是由于box出现了滚动条,你会发现visibelBox虽然定位了top=0px,但是也会随着滚动条滚动,说明当出现滚动条时,是根据textBox(撑开滚动条的盒子)来定位的