那么问题就来了,如何计算可视区域内需要渲染的元素,我们通过如下几步来实现虚拟滚动:
- 每一行的高度需要相同,方便计算
- 需要得知渲染的数据量(数组长度),可基于总量和每个元素的高度计算出容器整体的所需高度,这样就可以伪造一个真实的滚动条
- 获取可视区域的高度
- 在滚动事件触发后,滚动条的距顶距离也可以理解为这个数据量中的偏移量,再根据可视区域本身的高度,算出本次偏移的截止量,这样就得到了需要渲染的具体数据
- 如果类似于渲染一个宽表,单行可横向拆分为多列,那么在X轴上同理实现一次,就可以横向的虚拟滚动
【背景说明】假设实际开发中服务端一次响应10万条列表数据,此时设备屏幕只允许容纳10条,那么用户理论上只可以看见10条数据。此时如果前端将10万条数据全部渲染成DOM元素,可能造成程序卡顿,占用较大资源,非常影响用户体验,那么虚拟滚动技术就完美的解决了这一问题。
【虚拟滚动案例网址】虚拟滚动原理(未来此地址可能失效,失效后请直接复制源码学习)
【虚拟滚动的实现】
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- import CSS -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.13/vue.min.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<title>虚拟滚动原理</title>
</head>
<body>
<div id="app">
<el-row :gutter="10">
<el-col :xs="6" :sm="6" :md="5" :lg="4" :xl="2">
<el-button type="danger" @click="virtualScrolling(20)">20条</el-button>
</el-col>
<el-col :xs="6" :sm="6" :md="5" :lg="4" :xl="2">
<el-button type="primary" @click="virtualScrolling(100)">一百条</el-button>
</el-col>
<el-col :xs="6" :sm="6" :md="5" :lg="4" :xl="2">
<el-button type="success" @click="virtualScrolling(1000)">一千条</el-button>
</el-col>
<el-col :xs="6" :sm="6" :md="5" :lg="4" :xl="2">
<el-button @click="virtualScrolling(100000)">十万条</el-button>
</el-col>
</el-row>
<div class="wrap" @scroll="liScroll">
<ul class="ul_wrap" :style="`height:${ulHei}px`">
<li class="li_item" :style="`height:${liHei}px;transform:translateY(${ScroolNum}px)`"
v-for="item in liList" :key="item">
{{item}}
</li>
</ul>
</div>
</div>
</body>
<style>
.wrap {
height: 400px;
background-color: #fff;
overflow: scroll;
margin-top: 20px;
}
.li_item {
border: 1px red solid;
line-height: 50px;
}
</style>
<script>
new Vue({
el: '#app',
data(){
return {
liHei: 50,//li的高度
ulHei: 480,//ul的高度
liList: [],//真实展示的列表
scrollHei:0,//@scroll事件滚动的top值
ScroolNum: 0,//scrollHei能被li高度取余数的整数值。ScroolNum=scrollHei-(scrollHei%liHei)
showList: 0,//真实展示的条数
tableData: [],//全部数据的集合
lastTime:0,//最后时间
}
},
mounted () {
this.virtualScrolling(100)
},
methods: {
/**滚动监听 */
liScroll (e) {
if(new Date().getTime()-this.lastTime>40){//设置时间间隔,防止滚动事件高频触发消耗内存资源
this.ele = e;//保存元素,方便重置scrollTop值
this.scrollHei = e.target.scrollTop;//保存滚动条scrollTop值
this.ScroolNum = this.scrollHei - (this.scrollHei % this.l
**真题解析、进阶学习笔记、最新讲解视频、实战项目源码、学习路线大纲**
**详情关注公中号【编程进阶路】**
iHei);//获取已滚动到页面上方不可见的li元素的总高度(translateY的偏移高度)
let len = this.ScroolNum / this.liHei;//计算已经有多少个li滚动到页面上方(视图上方用户不可见的数量)
this.liList = this.tableData.slice(len, len + this.showList);//每次滚动事件后重新计算展示内容(截取的内容对应全部数据集的部分内容)
this.lastTime=new Date().getTime();//记录最后一次更新时间
}
},
/**初始化数据*/
virtualScrolling (num) {
let arr = [];//初始化数组
for (let i = 0; i < num; i++) {//计算给定数据量
arr.push(i+1)
}
this.tableData = arr;//全部数据集
this.showList = Math.floor(this.ulHei / this.liHei) + 4;//计算真实渲染的列表数量
this.liList = this.tableData.slice(0, this.showList);//初始化可视列表的内容
this.lastTime=new Date().getTime();//记录最后一次更新时间
this.$message({
message: `当前数据为${num}条`,
type: 'success'
});
if (!!this.ele) {//判断监听元素是否保存到ele字段中
this.ele.target.scrollTop = 0;//如果元素存在ele中则将scrollTop初始化为0;
this.ScroolNum=0;//初始化translateY的偏移高度
}
},
}
})
</script>
</html>
【实现原理】
1、获取滚动高度
2、列表单个item的高度
3、计算屏幕容纳几个item
4、计算滚动了几个item到顶部不可见区域
5、使用css3的transform属性将滚动到上方不可见区域的DOM元素偏移到可见区域,同时进行数据的更新(重绘操作节约性能)。
总结:虚拟滚动技术的实现是合理运用“回流必定发生重绘,而重绘不一定会引发回流”的理论进行实现。
读者福利
========
由于篇幅过长,就不展示所有面试题了,想要完整面试题目的朋友(另有小编自己整理的2024大厂高频面试题及答案附赠)