vue之大量数据时实现虚拟上下滚动元素无跳动

虚拟滚动的实现

等不及啦,跳转到全部代码

前提

虚拟滚动是一种动态渲染页面元素的技术。当页面需要展示大量数据时(例如一万条),不能一次性将所有数据渲染到页面上,否则会导致性能问题。因此,需要采用虚拟滚动技术,假装展示了全部数据,实际上只渲染了一部分。
1688235609787.png

步骤

首先,我们的网页的 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 的内容。

JavaScript 部分

通过使用 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.valuecurrentIndex.valueshowItemNum.value。它从 arr.value 数组中提取了一部分元素,这部分元素的起始索引是 currentIndex.value,要提取的元素个数是 showItemNum.value。最后,计算函数返回提取出的元素作为 showItem 的值。这样,每当 arr.valuecurrentIndex.valueshowItemNum.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.valueshowItemNum.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 部分整合在一起使用。通过这些代码,可以实现虚拟滚动效果,优化大量数据的渲染性能。
效果:
录屏网页测试.gif

全部代码

<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>
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值