虚拟列表-渲染 10 万条数据
简介
虚拟列表是一种优化长列表渲染的技术,它可以在保持流畅性的同时,渲染大量的数据。
在传统的列表渲染中,如果列表非常长,会导致渲染时间过长,页面卡顿,用户体验变得非常差。而虚拟列表则是只渲染可见区域内的数据,而非全部渲染,这样就可以大大提高渲染效率,保持页面流畅性。
应用场景
虚拟列表技术在大数据量列表渲染场景中应用广泛,例如电商商品列表、社交动态列表等。
虚拟列表实现原理
<script setup lang="ts">
import axios from 'axios'
import { computed, onMounted, ref } from 'vue'
type Item = {
id: number
name: string
}
// 所有的数据,比如这个数组存放了十万条数据
const allListData = ref<Item[]>([])
const itemHeight = ref(40) // 每一条(项)的高度,比如 40 像素
const count = ref(10) // 一屏展示几条数据
const startIndex = ref(0) // 开始位置的索引
const endIndex = ref(10) // 结束位置的索引
const topVal = ref(0) // 父元素滚动位置
// 计算展示的列表
const showListData = computed(() => allListData.value.slice(startIndex.value, endIndex.value))
// 获取十万条数据
const getData = async () => {
const res = await axios.get('http://localhost:3000/large-data')
allListData.value = res.data.data
}
// 初始化加载
onMounted(() => {
getData()
})
// 虚拟列表视口
const viewport = ref<HTMLDivElement>()
// 滚动这里可以加上节流,减少触发频次
const handleScroll = () => {
// 非空判断
if (!viewport.value) return
// 获取滚动距离
const scrollTop = viewport.value.scrollTop
// 计算起始下标和结束下标,用于 computed 计算
startIndex.value = Math.floor(scrollTop / itemHeight.value)
endIndex.value = startIndex.value + count.value
// 动态更改定位的 top 值,确保联动,动态展示相应内容
topVal.value = viewport.value.scrollTop
}
</script>
<template>
<h2>手写虚拟列表-原理{{ topVal }}</h2>
<!--
虚拟列表容器:类似“视口”,视口的高度取决于一次展示几条数据
比如视口只能看到10条数据,一条40像素,10条400像素
故,视口的高度为400像素,注意要开定位和滚动条
-->
<div
class="viewport"
ref="viewport"
@scroll="handleScroll"
:style="{ height: itemHeight * count + 'px' }"
>
<!-- 占位 dom 元素,其高度为所有的数据的总高度 -->
<div class="placeholder" :style="{ height: allListData.length * itemHeight + 'px' }"></div>
<!-- 内容区,展示10条数据,注意其定位的top值是变化的 -->
<div class="list" :style="{ top: topVal + 'px' }">
<!-- 每一条(项)数据 -->
<div
v-for="item in showListData"
:key="item.id"
class="item"
:style="{ height: itemHeight + 'px' }"
>
{{ item.name }}
</div>
</div>
</div>
</template>
<style scoped lang="scss">
// 虚拟列表容器盒子
.viewport {
box-sizing: border-box;
width: 240px;
border: solid 1px #000000;
// 开启滚动条
overflow-y: auto;
// 开启相对定位
position: relative;
.list {
width: 100%;
height: auto;
// 搭配使用绝对定位
position: absolute;
top: 0;
left: 0;
.item {
box-sizing: border-box;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
// 隔行变色
&:nth-child(even) {
background: #c7edcc;
}
&:nth-child(odd) {
background: pink;
}
}
}
}
</style>
VueUse 方案
pnpm install @vueuse/core
<script setup lang="ts">
import { useVirtualList } from '@vueuse/core'
import axios from 'axios'
import { onMounted, ref } from 'vue'
type Item = {
id: number
name: string
}
// 所有的数据,比如这个数组存放了十万条数据
const allListData = ref<Item[]>([])
// 获取十万条数据
const getData = async () => {
const res = await axios.get('http://localhost:3000/large-data')
allListData.value = res.data.data
}
// 初始化加载
onMounted(() => {
getData()
})
// 每一项的高度,比如 40 像素
const itemHeight = ref(40)
// vueuse方案:https://vueuse.org/core/useVirtualList/
const { list, containerProps, wrapperProps } = useVirtualList(allListData, {
itemHeight: itemHeight.value,
})
</script>
<template>
<h2>虚拟列表-VueUse实现</h2>
<div v-bind="containerProps" class="viewport">
<div v-bind="wrapperProps" class="list">
<div v-for="item in list" :key="item.data.id" class="item">
{{ item.data.name }}
</div>
</div>
</div>
</template>
<style scoped lang="scss">
// 虚拟列表容器盒子
.viewport {
box-sizing: border-box;
width: 240px;
height: 400px;
border: solid 1px #000000;
// 开启滚动条
overflow-y: auto;
// 开启相对定位
position: relative;
.list {
width: 100%;
height: auto;
// 搭配使用绝对定位
position: absolute;
top: 0;
left: 0;
.item {
box-sizing: border-box;
width: 100%;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
// 隔行变色
&:nth-child(even) {
background: #c7edcc;
}
&:nth-child(odd) {
background: pink;
}
}
}
}
</style>
vue-virtual-scroller 方案
pnpm install vue-virtual-scroller@next
<script setup lang="ts">
import axios from 'axios'
import { onMounted, ref } from 'vue'
// https://github.com/Akryum/vue-virtual-scroller
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
type Item = {
id: number
name: string
}
// 所有的数据,比如这个数组存放了十万条数据
const allListData = ref<Item[]>([])
// 获取十万条数据
const getData = async () => {
const res = await axios.get('http://localhost:3000/large-data')
allListData.value = res.data.data
}
// 初始化加载
onMounted(() => {
getData()
})
// 每一项的高度,比如 40 像素
const itemHeight = ref(40)
</script>
<template>
<h2>虚拟列表-vue-virtual-scroller实现</h2>
<RecycleScroller
class="viewport"
:items="allListData"
:item-size="itemHeight"
key-field="id"
v-slot="{ item }"
item-class="item"
>
{{ item.name }}
</RecycleScroller>
</template>
<style scoped lang="scss">
// 虚拟列表容器盒子
.viewport {
box-sizing: border-box;
width: 240px;
height: 400px;
border: solid 1px #000000;
// 开启滚动条
overflow-y: auto;
// 开启相对定位
position: relative;
:deep(.item) {
box-sizing: border-box;
width: 100%;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
// 隔行变色
&:nth-child(even) {
background: #c7edcc;
}
&:nth-child(odd) {
background: pink;
}
}
}
</style>
本文来自博客园,作者:jialiangzai,转载请注明原文链接:https://www.cnblogs.com/zsnhweb/articles/17779619.html

浙公网安备 33010602011771号