赞
踩
虚拟滚动是一种动态渲染页面元素的技术。当页面需要展示大量数据时(例如一万条),不能一次性将所有数据渲染到页面上,否则会导致性能问题。因此,需要采用虚拟滚动技术,假装展示了全部数据,实际上只渲染了一部分。
首先,我们的网页的 HTML 结构如下:
<div class="appbox" ref="appBox" @scroll="appBoxScroll($event)">
<div class="appbox-scroll" :style="{ height: containerHeight + 'px' }" style="position: relative;">
<div :style="{ transform: `translateY(${appBoxOffset}px)` }" style="position: absolute; width: 100%;">
<div class="boxcontent" v-for="(item, key) in showItem" :key="key">
{{ item }}
</div>
</div>
</div>
</div>
对应的 CSS 样式如下:
.appbox {
position: relative;
width: 200px;
height: 200px;
overflow: auto;
border: 1px solid #ccc;
}
.boxcontent {
height: 40px;
line-height: 40px;
border: 1px solid red;
}
<div class="appbox" ref="appBox" @scroll="appBoxScroll($event)">
:一个具有 CSS 类名为 “appbox” 的 <div>
元素。通过 ref
属性设置了一个引用名为 “appBox”,可以在 Vue 组件中通过 this.$refs.appBox
来引用该元素。通过 @scroll
事件绑定了一个滚动事件,当滚动事件触发时,会调用 Vue 组件中的 appBoxScroll
方法,并将事件对象 $event
作为参数传递给该方法。
<div class="appbox-scroll" :style="{ height: containerHeight + 'px' }" style="position: relative;">
:一个具有 CSS 类名为 “appbox-scroll” 的 <div>
元素。通过动态绑定的方式设置了该元素的高度,绑定的值为 containerHeight
加上 ‘px’ 单位。containerHeight
是一个在 Vue 组件中定义的响应式数据,其值会随着数据的变化而更新。
<div :style="{ transform:
translateY(${appBoxOffset}px) }" style="position: absolute; width: 100%;">
:该 <div>
元素的样式部分包含了两个部分:
:style="{ transform:
translateY(${appBoxOffset}px) }"
:使用动态绑定的方式设置该 <div>
元素的变换样式,通过 appBoxOffset
的值来设置 translateY
的偏移量。appBoxOffset
是一个在 Vue 组件中定义的响应式数据,其值会随着数据的变化而更新。
style="position: absolute; width: 100%;"
:设置该 <div>
元素的绝对定位和宽度为 100%。
<div class="boxcontent" v-for="(item, key) in showItem" :key="key">
:一个具有 CSS 类名为 “boxcontent” 的 <div>
元素,根据 v-for
指令生成多个。v-for
指令根据 showItem
数组的内容循环生成多个 <div class="boxcontent">
元素。showItem
是一个在 Vue 组件中定义的计算属性,根据条件切片处理数据后返回一个新的数组。
{{ item }}
:在每个 <div>
元素中显示当前循环项 item
的内容。
通过使用 ref
,我们可以创建具有响应性的数据引用。使用 computed
,我们可以创建根据其他响应式数据自动计算的属性。而 nextTick
则用于在 DOM 更新后执行异步操作。
import { ref, computed, nextTick } from "vue";
下面的代码使用 computed
函数创建了一个计算属性 showItem
。计算属性是根据其他响应式数据自动计算的属性。
const showItem = computed(() => {
return [...arr.value.slice(currentIndex.value, showItemNum.value + currentIndex.value + 1)];
});
在这里,showItem
的计算函数使用了三个响应式数据:arr.value
、currentIndex.value
和 showItemNum.value
。它从 arr.value
数组中提取了一部分元素,这部分元素的起始索引是 currentIndex.value
,要提取的元素个数是 showItemNum.value
。最后,计算函数返回提取出的元素作为 showItem
的值。这样,每当 arr.value
、currentIndex.value
或 showItemNum.value
发生变化时,showItem
的值会自动重新计算。
接下来是一些初始变量的定义:
// 设置1W条模拟数据 const count = ref(10000); let arr = ref([]); for (let index = 0; index < count.value; index++) { arr.value.push(index); } // 容器真实高度,给增加滚动条,假装渲染数据多 let containerHeight = ref(arr.value.length * 40); // 当前状态索引 let currentIndex = ref(0); // 应该显示的 DOM 数量 let showItemNum = ref(0); // 容器 DOM 节点 const appBox = ref(null); // 容器视窗高度 let appBoxHeight = ref(0); // 容器内部元素 DOM 节点下降偏移 let appBoxOffset = ref(0); // 老高度 let oldOffset = 0;
在回调函数中,首先通过 appBox.value.clientHeight
获取了容器的高度,并将其赋值给 appBoxHeight.value
。接下来,通过运算 Math.ceil(appBoxHeight.value / 40)
,计算出应该显示的 DOM 元素的数量,并将结果赋值给 showItemNum.value
。这里每个 DOM 元素的高度是 40 像素。通过将这两个值赋给响应式数据 appBoxHeight.value
和 showItemNum.value
,可以使它们成为计算属性的依赖项,从而触发计算属性的重新计算。
nextTick(() => {
// 获取容器高度
appBoxHeight.value = appBox.value.clientHeight;
// 运算出应该显示的 DOM 数量
showItemNum.value = Math.ceil(appBoxHeight.value / 40);
});
最后是关键的方法函数代码,其中有注释帮助理解:
const appBoxScroll = (e) => { let tempNum = 0; let scrollTop = e.target.scrollTop; // 判断向下还是向上滑动 if (scrollTop < oldOffset) { // 计算当前状态的索引 tempNum = Math.floor(scrollTop / 40); // 当前状态的索引发生变化才触发视图层刷新 if (tempNum !== currentIndex.value && scrollTop > 40) { currentIndex.value = tempNum; appBoxOffset.value = scrollTop - 40; } else if (scrollTop <= 40) { appBoxOffset.value = 0; currentIndex.value = 0; } } else { // 计算当前状态的索引 tempNum = Math.floor(e.target.scrollTop / 40); // 当前状态的索引发生变化才触发视图层刷新 if (tempNum !== currentIndex.value) { currentIndex.value = tempNum; appBoxOffset.value = scrollTop; } } // 记录老高度 oldOffset = scrollTop; };
以上就是实现虚拟滚动的代码。在 Vue 组件中,可以将 HTML、CSS 和 JavaScript 部分整合在一起使用。通过这些代码,可以实现虚拟滚动效果,优化大量数据的渲染性能。
效果:
<template> <div class="appbox" ref="appBox" @scroll="appBoxScroll($event)"> <div class="appbox-scroll" :style="{ height: containerHeight + 'px' }" style="position: relative;"> <div :style="{ transform: `translateY(${appBoxOffset}px)` }" style="position: absolute; width: 100%;"> <div class="boxcontent" v-for="(item, key) in showItem" :key="key"> {{ item }} </div> </div> </div> </div> </template> <script setup> import { ref, computed, nextTick } from "vue"; const showItem = computed(() => { return [...arr.value.slice(currentIndex.value, showItemNum.value + currentIndex.value + 1)]; }); //设置1W条模拟数据 const count = ref(10000); let arr = ref([]); for (let index = 0; index < count.value; index++) { arr.value.push(index); } //容器真实高度,给增加滚动条,假装渲染数据多 let containerHeight = ref(arr.value.length * 40); // 当前状态索引 let currentIndex = ref(0); // 应该显示的 DOM 数量 let showItemNum = ref(0); // 容器dom节点 const appBox = ref(null); // 容器视窗高度 let appBoxHeight = ref(0); // 容器内部元素dom节点下降偏移 let appBoxOffset = ref(0); // 老高度 let oldOffset = 0; nextTick(() => { //获取容器高度 appBoxHeight.value = appBox.value.clientHeight; //运算出应该显示的 DOM 数量 showItemNum.value = Math.ceil(appBoxHeight.value / 40); }); const appBoxScroll = (e) => { let tempNum = 0; let scrollTop = e.target.scrollTop; // 判断向下还是向上滑动 if (scrollTop < oldOffset) { //计算当前状态的索引 tempNum = Math.floor(scrollTop / 40); //当前状态的索引发生变化才触发视图层刷新 if (tempNum !== currentIndex.value && scrollTop > 40) { currentIndex.value = tempNum; appBoxOffset.value = scrollTop - 40; } else if (scrollTop <= 40) { appBoxOffset.value = 0; currentIndex.value = 0; } } else { //计算当前状态的索引 tempNum = Math.floor(e.target.scrollTop / 40); //当前状态的索引发生变化才触发视图层刷新 if (tempNum !== currentIndex.value) { currentIndex.value = tempNum appBoxOffset.value = scrollTop; } } // 记录老高度 oldOffset = scrollTop; }; </script> <style> .appbox { position: relative; width: 200px; height: 200px; overflow: auto; border: 1px solid #ccc; } .boxcontent { height: 40px; line-height: 40px; border: 1px solid red; } </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。