项目场景:
当前页显示100w多条数据,不做分页的情况进行渲染。加载和渲染页面会非常慢,滚动也非常卡顿
解决方案:
1.渲染可视窗口的列表,其他列表不进行渲染。通过修改偏移量高度进行滚动列表。
2.分段插入,同时使用window.requestAnimationFrame()函数,按帧执行你插入数据的函数
vue2版本
virtualList 组件包
注意:
组件必传containerHeight高度(容器高度)和数据列表listData
<template>
<div>
<div ref="listRef" :style="{height:containerHeight}" class="listContainer" @scroll="scrollEvent($event)">
<div class="listPhantom" :style="{ height: computedListHeight + 'px' }"></div>
<div class="list" ref="infiniteListRef" :style="{ transform: computedGetTransform }">
<div ref="items"
class="listItem"
v-for="(item,key) in computedVisibleData"
:key="key+'-'+item?.id"
:style="{height:'100%' }"
><slot :data="item" /></div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'virtualList',
props: {
//所有列表数据
listData:{
type:Array,
default:()=>[]
},
//容器高度
containerHeight:{
type:String,
default:"100%"
}
},
watch:{
//监听列表数据
listData:{
handler() {
//修改每一个列的高度
this.$nextTick(()=>{
//获取每个列表高度
this.itemHeight = this.$refs.items ? this.$refs.items[0].offsetHeight:1
this.end = this.start + this.computedVisibleCount;
})
},
deep: true,
immediate: true
}
},
computed:{
//获取真实显示列表数据
computedVisibleData(){
return this.listData.slice(this.start, Math.min(this.end,this.listData.length));
},
//列表总高度
computedListHeight(){
return BigInt(this.listData.length * this.itemHeight);
},
//可显示的列表项数
computedVisibleCount(){
return Math.ceil(this.screenHeight / this.itemHeight)
},
//偏移量对应的style
computedGetTransform(){
return `translate3d(0,${this.startOffset}px,0)`;
}
},
data() {
return {
//每列高度
itemHeight:1,
//可视区域高度
screenHeight:0,
//偏移量
startOffset:0,
//起始索引
start:0,
//结束索引
end:null,
};
},
methods: {
scrollEvent() {
//当前滚动位置
let scrollTop = this.$refs.listRef.scrollTop;
//此时的开始索引
this.start = Math.floor(scrollTop / this.itemHeight);
//此时的结束索引
this.end = this.start + this.computedVisibleCount;
//此时的偏移量
this.startOffset = scrollTop - (scrollTop % this.itemHeight);
},
//页面初始化
handleInit(){
this.screenHeight = this.$el.clientHeight;//客户端高度
this.start = 0;//列表开始索引
this.end = this.start + this.computedVisibleCount;//列表结束索引
}
},
mounted() {
this.handleInit()
},
}
</script>
<style scoped lang="less">
.listContainer {
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
}
.listPhantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.list {
left: 0;
right: 0;
top: 0;
position: absolute;
text-align: center;
z-index:10;
}
.listItem {
padding: 10px;
color: #555;
box-sizing: border-box;
}
</style>
app.vue文件
分段插入数据
<template>
<div id="app">
<div style="background-color: yellow">
<VirtualList ref="testRef" :containerHeight="containerHeight" :listData="rows" >
<template slot-scope="row">
<div style="border-bottom: 1px solid red;">
{{ row.data.id }}
</div>
</template>
</VirtualList>
</div>
</div>
</template>
<script>
import VirtualList from './components/virtualList.vue';
export default {
data(){
return {
once:0,//每次插入的数量
containerHeight:0,
total:0,//总条数
countRender:0,//已经渲染次数
loopCount:0,//需要插入的次数
rows:[]
}
},
components:{VirtualList},
created() {
this.containerHeight = 300 +'px';
},
mounted() {
this.handleInit();
},
methods:{
handleInit(){
setTimeout(()=>{
// 百万条数据
this.total = 10000000000;
// 单次插入 可自定义
this.once = 20;
// 需要插入的次数 向上取整
this.loopCount = Math.ceil(this.total / this.once);
// 当前渲染次数
this.countRender = 0;
this.handleRender();
},500)
},
//百万数据分段插入
handleRender(){
for (let i = 0; i < this.once; i++) {
this.rows.push({id:this.countRender+'-'+i})
}
// 渲染次数加1,控制递归的次数
this.countRender++;
if (this.countRender < this.loopCount) {
window.requestAnimationFrame(this.handleRender);
}
}
}
}
</script>
<style>
#app {
height: 500px;
width: 100%;
}
</style>
vue3版本
virtualList 组件包
注意:
组件必传data高度(容器高度)和数据列表containerHeight
<!--
*- coding = utf-8 -*-
#@Time : 2023-06-29 23:59
#@Author : 管茂良
#@File : virtualList.vue
#@web : www.php-china.com
#@Software: WebStorm
-->
<template>
<div>
<div class="listContainer" :style="{height:props.containerHeight}" @scroll="handleScroll">
<div class="listContainerPhantom" :style="{ height: listHeight + 'px' }"></div>
<div class="listContainerList" :style="{ transform: computedTransform }">
<div ref="listItemRef" :key="index+'-'+item?.id" v-for="(item,index) in computedVisibleData">
<slot :data="item"></slot>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {computed, defineProps, nextTick, onMounted, ref, watch, withDefaults} from "vue";
let startNum = ref(0)
let endNum = ref(0)
let startOffset = ref(0);//偏移量
let listHeight = ref(0);//列表总高度
let listItemHeight = ref(0);//每项高度
let listItemRef = ref(null);
interface propsInterface{
data?:any //列表数据
containerHeight?:string //容器高度
}
let props = withDefaults(defineProps<propsInterface>(),{
//所有数据
data:[],
containerHeight:"",
})
const computedTransform = computed(()=>{
return `translate3d(0,${startOffset.value}px,0)`;
})
//可看得见的数据
const computedVisibleData = computed(()=>{
return props.data.slice(startNum.value,Math.min(endNum.value,props.data.length))
})
const handleScroll = ()=>{
let listContainer = document.querySelector(".listContainer");//列表容器
let listContainerHeight = listContainer?.offsetHeight
//可显示的数量
let showNum = Math.ceil(listContainerHeight / listItemHeight.value)
//滚动高度
let contentScrollTop = Math.floor(listContainer.scrollTop);
let curNum = Math.ceil(contentScrollTop / listItemHeight.value);
startNum.value = curNum
endNum.value = showNum + startNum.value
//滚动高度减去空隙
startOffset.value = contentScrollTop - (contentScrollTop%showNum)
}
const handleInit = ()=>{
let listContainer = document.querySelector(".listContainer");//列表容器
let listItem = listItemRef.value ;//每一列
let listContainerHeight = listContainer?.offsetHeight
listItemHeight.value = listItem ? listItem[0]?.offsetHeight : 1
//列表总高度
listHeight.value = props.data.length*listItemHeight.value
startNum.value = 0
let showNum = Math.ceil(listContainerHeight / listItemHeight.value)
endNum.value = startNum.value + showNum
}
//监听列表高度,有变化,重新初始化
watch(()=>props.data,(value)=>{
handleInit()
},{immediate:true,deep:true})
onMounted(()=>{
handleInit()
})
</script>
<style scoped lang="less">
.listContainer{
height:400px;
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
.listContainerPhantom{
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.listContainerList{
left: 0;
right: 0;
top: 0;
position: absolute;
text-align: center;
}
}
</style>
app.vue文件
分段插入数据
<template>
<div class="app-container">
<VirtualList :data="data" :containerHeight="virtualListHeight" >
<template #default="row">
<div class="listItem u-f u-f-ac u-f-spa" >
{{row.data.id}}
</div>
</template>
</VirtualList>
</div>
</template>
<script setup lang="ts">
import {defineAsyncComponent, onMounted, ref} from "vue";
const VirtualList = defineAsyncComponent(()=>import("@/components/virtualList/index"))
let once = ref(0)//每次插入的数量
let total = ref(0)//总条数
let countRender = ref(0)//已经渲染次数
let loopCount = ref(0)//需要插入的次数
let data = ref([])
let virtualListHeight = ref("")
const handleInit = () => {
data.value.length = 0;
setTimeout(()=>{
// 百万条数据
total.value = 10000;
// 单次插入 可自定义
once.value = 20;
// 需要插入的次数 向上取整
loopCount.value = Math.ceil(total.value / once.value);
// 当前渲染次数
countRender.value = 0;
handleRender();
},500)
}
//百万数据分段插入
const handleRender = () => {
for (let i = 0; i < once.value; i++) {
data.value.push({id:countRender.value+'-'+i})
}
// 渲染次数加1,控制递归的次数
countRender.value++;
if (countRender.value < loopCount.value) {
window.requestAnimationFrame(handleRender);
}
}
onMounted(()=>{
virtualListHeight.value = (document.documentElement.offsetHeight-400)+"px"
handleInit()
})
</script>
<style scoped lang="less">
.app-container{
background-color: gray;
height:500px;
width: 500px;
}
.listItem{
border-bottom:1px solid red;
padding:10px;
}
</style>
最终效果:
案例源码
https://gitee.com/derekgo/tool-collection/tree/dev/vue/virtualList
✨ 踩坑不易,还希望各位大佬支持一下 \textcolor{gray}{踩坑不易,还希望各位大佬支持一下} 踩坑不易,还希望各位大佬支持一下
📃 个人主页: \textcolor{green}{个人主页:} 个人主页: 沉默小管
📃 个人网站: \textcolor{green}{个人网站:} 个人网站: 沉默小管
📃 个人导航网站: \textcolor{green}{个人导航网站:} 个人导航网站: 沉默小管导航网
📃 我的开源项目: \textcolor{green}{我的开源项目:} 我的开源项目: vueCms.cn
🔥 技术交流 Q Q 群: 837051545 \textcolor{green}{技术交流QQ群:837051545} 技术交流QQ群:837051545
👍 点赞,你的认可是我创作的动力! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向! \textcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富! \textcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!
如果有不懂可以留言,我看到了应该会回复
如有错误,请多多指教