重绘(repaint):只是当页面的颜色、透明度等信息发生变化时会导致重绘。例如:color、background-color、visibility等,结构不变。
回流、重排(reflow):整个dom树重新渲染。
假设实际开发中服务端一次响应10万条列表数据,此时设备屏幕只允许容纳10条,那么用户理论上只可以看见10条数据。此时如果前端将10万条数据全部渲染成DOM元素,可能造成程序卡顿,占用较大资源,非常影响用户体验,那么虚拟滚动技术就完美的解决了这一问题。
【虚拟滚动的实现】
1、获取滚动高度
2、列表单个item的高度
3、计算屏幕容纳几个item
4、计算滚动了几个item到顶部不可见区域
5、使用css3的transform属性将滚动到上方不可见区域的DOM元素偏移到可见区域,同时进行数据的更新(重绘操作节约性能)。
使用 react,vue 等页面框架来编写 view 页面,采用虚拟 DOM 技术,极可能的将多次重排浓缩成一次
vue2代码:
<!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.liHei);//获取已滚动到页面上方不可见的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>
vue3:
<script setup>
import { ref, computed, nextTick } from "vue";
//设置10W条模拟数据
const count = ref(100000);let arr = ref([]);
for (let index = 0; index < count.value; index++) {
arr.value.push(index);
}
//容器真实高度
let containerHeight = ref(arr.value.length * 40);
//当前状态的索引
let startKey = ref(0);
//视窗内应该显示的 DOM 数量
let showItemNum = ref(0);
//容器dom节点
const wrapper = ref(null);
//容器高度
let wrapperHeight = ref(0);
nextTick(() => {
//获取容器高度
wrapperHeight.value = wrapper.value.clientHeight;
//运算出应该显示的 DOM 数量
showItemNum.value = Math.ceil(wrapperHeight.value / 40);
});
//片段容器偏移量
let scrollTopWrapper = ref(0);
//滚动事件
const wrapperScroll = (e) => {
//计算当前状态的索引
let tempNum = Math.floor(e.target.scrollTop / 40);
//当前状态的索引发生变化才触发视图层刷新
if (tempNum !== startKey.value) {
startKey.value = tempNum
scrollTopWrapper.value = e.target.scrollTop;
}
};
//对数据进行切片处理方法
const showItem = computed(() => {
return [...arr.value.slice(startKey.value, showItemNum.value + startKey.value + 3)];
});
</script>
<template>
<div class="wrapper" ref="wrapper" @scroll="wrapperScroll($event)">
<div class="wrapper-scroll" :style="{ height: containerHeight + 'px' }" style="position: relative;" >
<div :style="{ transform: `translateY(${scrollTopWrapper}px)` }" style="position: absolute; width: 100%;" >
<div v-for="(item, key) in showItem" :key="key" style="height:40px;line-height:40px" >
{{item}}
</div>
</div>
</div>
</div>
</template>
<style>
.wrapper {
position: relative;
width: 200px;
height: 200px;
overflow: auto;
border: 1px solid #ccc;
}
</style>
其他Css优化方法:
1,首推的是合并css文件,如果页面加载10个css文件,每个文件1k,那么也要比只加载一个100k的css文件慢。
2,减少css嵌套,最好不要套三层以上。
3,不要在ID选择器前面进行嵌套,ID本来就是唯一的而且人家权值那么大,嵌套完全是浪费性能。
4,建立公共样式类,把相同样式提取出来作为公共类使用,比如我们常用的清除浮动等。
5,减少通配符*或者类似[hidden=“true”]这类选择器的使用,挨个查找所有…这性能能好吗?当然重置样式这些必须 的东西是不能少的。
6,巧妙运用css的继承机制,如果父节点定义了,子节点就无需定义。
7,拆分出公共css文件,对于比较大的项目我们可以将大部分页面的公共结构的样式提取出来放到单独css文件里, 这样一次下载后就放到缓存里,当然这种做法会增加请求,具体做法应以实际情况而定。
8,不用css表达式,表达式只是让你的代码显得更加炫酷,但是他对性能的浪费可能是超乎你的想象的。
9,少用css rest,可能你会觉得重置样式是规范,但是其实其中有很多的操作是不必要不友好的,有需求有兴趣的 朋友可以选择normolize.css
10,cssSprite,合成所有icon图片,用宽高加上bacgroud-position的背景图方式显现出我们要的icon图,这是一种 十分实用的技巧,极大减少了http请求。
11,当然我们还需要一些善后工作,CSS压缩(这里提供一个在线压缩 YUI Compressor ,当然你会用其他工具来压缩是十 分好的),
12,GZIP压缩,Gzip是一种流行的文件压缩算法
版权归原作者 前端段 所有, 如有侵权,请联系我们删除。