欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在Vue3中實現(xiàn)虛擬列表的方法示例

 更新時間:2025年01月15日 09:21:05   作者:程序員張張  
文章主要介紹在 Vue3 中實現(xiàn)虛擬列表的方法,包括原理和代碼實現(xiàn),原理是只渲染可視區(qū)域內(nèi)的列表項,通過設(shè)置子數(shù)據(jù)項高度、計算可視區(qū)域、渲染可視區(qū)域、滾動監(jiān)聽、設(shè)置緩沖列表項等提升性能,感興趣的小伙伴跟著小編一起來看看吧

引言

在開發(fā)過程中,我們有時會遇到數(shù)據(jù)量較大的情況,這會導(dǎo)致大量數(shù)據(jù)同時加載到頁面,從而生成過多的 DOM 元素。這種情況不僅會導(dǎo)致頁面卡頓,甚至可能導(dǎo)致瀏覽器直接崩潰。給用戶體驗帶來極大的負(fù)面影響。為了解決這一問題,我們可以采用虛擬列表技術(shù),通過只渲染可視區(qū)域內(nèi)的元素,顯著提升頁面的性能和用戶體驗。

現(xiàn)在網(wǎng)上有許多現(xiàn)成的虛擬列表第三方插件庫,我們可以直接使用這些庫。然而,這邊我打算自己動手去實現(xiàn)虛擬列表功能。在之前的 Vue 2 項目中,我已經(jīng)實現(xiàn)過類似的功能,這次我打算利用 Vue 3 來重新實現(xiàn),并將其封裝成一個公共組件。

虛擬列表的基本原理

虛擬列表通過只渲染當(dāng)前可視區(qū)域內(nèi)的列表項,從而提高長列表加載到頁面的性能。

  • 設(shè)置子數(shù)據(jù)項高度:確定子數(shù)據(jù)項的具體高度。以確定當(dāng)前區(qū)域內(nèi)需要渲染的列表項。
  • 計算可視區(qū)域高度:確定當(dāng)前可視區(qū)域內(nèi)可渲染多少條子數(shù)據(jù)項,計算起始下標(biāo)、結(jié)束下標(biāo)。避免渲染整個列表。
  • 渲染可視區(qū)域:保持渲染的DOM節(jié)點數(shù)量始終在一個較小的范圍內(nèi),通過動態(tài)調(diào)整渲染內(nèi)容的位置,保持列表高度完整且滾動條能正常滾動。
  • 滾動監(jiān)聽:監(jiān)聽容器的滾動事件,實時獲取滾動位置,通過滾動位置實時更新可視區(qū)域范圍,動態(tài)渲染對應(yīng)列表項。
  • 設(shè)置緩沖列表項:在可視區(qū)域的上下各增加一定數(shù)量的緩沖列表項,提前加載即將進(jìn)入可視區(qū)域的列表項,避免滾動時出現(xiàn)空白以及卡頓的情況。

好的!接下來,我們將通過代碼一步步實現(xiàn)上述功能,完整呈現(xiàn)虛擬列表的核心邏輯和效果。

代碼實現(xiàn)

1、設(shè)置子數(shù)據(jù)項的高度

子數(shù)據(jù)項的高度是固定值,所以這里就定義了個變量。(注:子數(shù)據(jù)項的高度與css中的高度保持一致)代碼如下:

<script lang="ts" setup>
// 子數(shù)據(jù)項高度
const itemHeight = 40
</script>

2、計算可視區(qū)域高度、起始下標(biāo)、結(jié)束下標(biāo)

因為下面會通過滾動條的高度去計算詳細(xì)的值。所以這里我們的起始下標(biāo)和結(jié)束下標(biāo)使用計算屬性去定義。代碼如下:

<script lang="ts" setup>
// 可視區(qū)域的高度
const viewHeight = ref(0)

// ref虛擬列表容器dom
const virtualContainer = ref<HTMLElement | null>(null)
  
// 在dom加載完成后,通過ref去獲取可視區(qū)域的高度
onMounted(() => {
	nextTick(() => {
		viewHeight.value = virtualContainer.value?.clientHeight ?? 0
	})
})

// 虛擬列表真實展示數(shù)據(jù):起始下標(biāo)  
const start = computed(() => {
	return 0
})
// 虛擬列表真實展示數(shù)據(jù):結(jié)束下標(biāo)  
const end = computed(() => {
	return viewHeight.value / itemHeight
})
</script>

3、渲染可視區(qū)域

paddingAttr 的目的是保持列表的高度完整,并確保滾動條能夠正常滾動。由于實際渲染的 DOM 元素較少,可能導(dǎo)致滾動條位置異常,因此需要通過設(shè)置 padding 來撐起容器的高度。此外,也可以使用 transformposition 來實現(xiàn)這一效果。代碼如下:

<div ref="virtualContainer" @scroll="onScroll" class="virtual-container">
  <div class="virtual-list">
    <div class="virtual-item" v-for="item in virtualData" :key="item.id">
      <div class="item">{{ item.title }}</div>
    </div>
  </div>
</div>
<script lang="ts" setup>
// 大數(shù)據(jù)數(shù)組
const dataList = reactive<any[]>([])
for (let i = 0; i < 100000; i++) {
	dataList.push({ id: i, title: `標(biāo)題${i}` })
}  
// 計算虛擬列表的padding(保持列表高度完整且滾動條能正常滾動)
const paddingAttr = computed(() => {
	const paddingTop = start.value * itemHeight
	const paddingBottom = (dataList.length - over.value) * itemHeight
	return `${paddingTop}px 0 ${paddingBottom}px`
})
// 虛擬列表真實展示數(shù)據(jù)
const virtualData = computed(() => {
	return dataList.slice(start.value, over.value)
})
</script>
<style lang="scss" scoped>
.virtual-container {
	overflow-y: auto;
	height: 100%;

	.virtual-list {
		padding: v-bind(paddingAttr);

		.virtual-item {
			text-align: center;
			height: 30px;
			line-height: 30px;
			background: #84bbfc;
			margin-bottom: 10px;
		}
	}
}
</style> 

4、滾動監(jiān)聽

上面我們初步的定義了起始下標(biāo)、結(jié)束下標(biāo),但那并不滿足我們的需求,這邊我們通過監(jiān)聽滾動事件,獲取到滾動條位置,通過滾動條位置去重新計算起始下標(biāo)、結(jié)束下標(biāo)。代碼如下:

<script lang="ts" setup>
// 滾動條距離頂部距離
const scrollTop = ref(0) 

// 虛擬列表真實展示數(shù)據(jù):起始下標(biāo)
const start = computed(() => {
	const s = Math.floor(scrollTop.value / itemHeight)
	return Math.max(0, s)
})

// 虛擬列表真實展示數(shù)據(jù):結(jié)束下標(biāo)
const over = computed(() => {
	const o = Math.floor((scrollTop.value + viewHeight.value + 1) / itemHeight)
	return Math.min(dataList.length, o)
})  
// 監(jiān)聽滾動條距離頂部距離,實時更新
const onScroll = () => {
	scrollTop.value = virtualContainer.value?.scrollTop ?? 0
} 
</script>

5、設(shè)置緩沖列表項

這里給起始下標(biāo)和結(jié)束下標(biāo),各自加減一個固定值,我這邊設(shè)置的值是5,這邊可以設(shè)置成其他值,但不能太大會影響性能。太小的話滾動會卡頓和出現(xiàn)白屏問題。代碼如下:

<script lang="ts" setup>
// 虛擬列表真實展示數(shù)據(jù):起始下標(biāo)
const start = computed(() => {
	const s = Math.floor(scrollTop.value / itemHeight - 5)
	return Math.max(0, s)
})

// 虛擬列表真實展示數(shù)據(jù):結(jié)束下標(biāo)
const over = computed(() => {
	const o = Math.floor((scrollTop.value + viewHeight.value + 1) / itemHeight + 5)
	return Math.min(dataList.length, o)
})  
</script>

好了,下面是虛擬列表的完整的代碼:

<template>
	<div ref="virtualContainer" @scroll="onScroll" class="virtual-container">
		<div class="virtual-list">
			<div class="virtual-item" v-for="item in virtualData" :key="item.id">
				<div class="item">{{ item.title }}</div>
			</div>
		</div>
	</div>
</template>

<script lang="ts" setup>
import { computed, nextTick, onMounted, ref, reactive } from 'vue'

/**
 * 虛擬列表的每一項的高度
 */
const itemHeight = 40

const dataList = reactive<any[]>([])
for (let i = 0; i < 100000; i++) {
	dataList.push({ id: i, title: `標(biāo)題${i}` })
}

/**
 * 滾動條距離頂部距離
 */
const scrollTop = ref(0)

/**
 * ref虛擬列表容器dom
 */
const virtualContainer = ref<HTMLElement | null>(null)
/**
 * 可視區(qū)域的高度
 */
const viewHeight = ref(0)
// 在dom加載完成后,獲取可視區(qū)域的高度
onMounted(() => {
	nextTick(() => {
		viewHeight.value = virtualContainer.value?.clientHeight ?? 0
	})
})

/**
 * 虛擬列表真實展示數(shù)據(jù):起始下標(biāo)
 */
const start = computed(() => {
	const s = Math.floor(scrollTop.value / itemHeight)
	return Math.max(0, s)
})

/**
 * 虛擬列表真實展示數(shù)據(jù):結(jié)束下標(biāo)
 */
const over = computed(() => {
	const o = Math.floor((scrollTop.value + viewHeight.value + 1) / itemHeight)
	return Math.min(dataList.length, o)
})

/**
 * 計算虛擬列表的padding(保持列表高度完整且滾動條能正常滾動)
 */
const paddingAttr = computed(() => {
	const paddingTop = start.value * itemHeight
	const paddingBottom = (dataList.length - over.value) * itemHeight
	return `${paddingTop}px 0 ${paddingBottom}px`
})

/**
 * 虛擬列表真實展示數(shù)據(jù)
 */
const virtualData = computed(() => {
	return dataList.slice(start.value, over.value)
})

/**
 * 監(jiān)聽滾動條距離頂部距離,實時更新
 */
const onScroll = () => {
	scrollTop.value = virtualContainer.value?.scrollTop ?? 0
}
</script>

<style lang="scss" scoped>
.virtual-container {
	overflow-y: auto;
	height: 100%;

	.virtual-list {
		padding: v-bind(paddingAttr);

		.virtual-item {
			text-align: center;
			height: 30px;
			line-height: 30px;
			background: #84bbfc;
			margin-bottom: 10px;
		}
	}
}

::-webkit-scrollbar {
	width: 12px;
	height: 12px;
	background: #ffffff;
	border-radius: 6px;
}

::-webkit-scrollbar-thumb {
	background: #00a6ff;
	border-radius: 6px;
}
</style>

示例:

組件封裝

上面我們完成了虛擬列表的功能實現(xiàn),但是呢,在現(xiàn)實的開發(fā)中我們會遇到不止一個長列表的需求,每一個都這么寫,會有很多冗余的代碼,而且很麻煩。所以在這里我們將其封裝成一個公共的組件。以簡化我們?nèi)粘i_發(fā)的代碼量和時間成本。

這邊封裝組件的邏輯和上面基本一致,我就不多贅述了,直接上代碼:

<template>
	<div ref="virtualContainer" @scroll="onScroll" class="virtual-container">
		<div class="virtual-list">
			<slot v-if="slotDefault" name="default" :dataList="virtualData"></slot>
			<template v-else>
				<div
					class="virtual-item"
					v-for="item in virtualData"
					:key="item[keyField]"
					:style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
				>
					<slot name="item" :item="item"></slot>
				</div>
			</template>
		</div>
	</div>
</template>

<script lang="ts" setup name="VirtualList">
import { withDefaults, defineProps, computed, nextTick, onMounted, ref, useSlots } from 'vue'

/**
 * 虛擬列表defineProps接口(類型約束)
 * @param dataList 數(shù)據(jù)列表
 * @param keyField 每一項的唯一標(biāo)識key
 * @param itemHeight 每一項的高度
 * @param containerHeight 容器高度
 */
interface virtualProps {
	dataList: any[]
	keyField?: string
	itemHeight?: number
	containerHeight?: string
}

/**
 * 父組件傳入的值
 * withDefaults 為props設(shè)置默認(rèn)值
 */
const { dataList, keyField, itemHeight, containerHeight } = withDefaults(defineProps<virtualProps>(), {
	keyField: 'id',
	itemHeight: 40,
	containerHeight: '100%'
})

/**
 * 滾動條距離頂部距離
 */
const scrollTop = ref(0)

/**
 * ref虛擬列表容器dom
 */
const virtualContainer = ref<HTMLElement | null>(null)
/**
 * 可視區(qū)域的高度
 */
const viewHeight = ref(0)

onMounted(() => {
	nextTick(() => {
		viewHeight.value = virtualContainer.value?.clientHeight ?? 0
	})
})

/**
 * 虛擬列表真實展示數(shù)據(jù):起始下標(biāo)
 */
const start = computed(() => {
	const s = Math.floor(scrollTop.value / itemHeight - 5)
	return Math.max(0, s)
})

/**
 * 虛擬列表真實展示數(shù)據(jù):結(jié)束下標(biāo)
 */
const over = computed(() => {
	const o = Math.floor((scrollTop.value + viewHeight.value + 1) / itemHeight + 5)
	return Math.min(dataList.length, o)
})

/**
 * 計算虛擬列表的padding(保持列表高度完整且滾動條能正常滾動)
 */
const paddingAttr = computed(() => {
	const paddingTop = start.value * itemHeight
	const paddingBottom = (dataList.length - over.value) * itemHeight
	return `${paddingTop}px 0 ${paddingBottom}px`
})

/**
 * 虛擬列表真實展示數(shù)據(jù)
 */
const virtualData = computed(() => {
	return dataList.slice(start.value, over.value)
})

/**
 * 監(jiān)聽滾動條距離頂部距離,實時更新
 */
const onScroll = () => {
	scrollTop.value = virtualContainer.value?.scrollTop ?? 0
}

/**
 * 獲取默認(rèn)插槽
 */
const slotDefault = useSlots().default
</script>

<style lang="scss" scoped>
.virtual-container {
	overflow-y: auto;
	height: v-bind(containerHeight);

	.virtual-list {
		padding: v-bind(paddingAttr);

		.virtual-item {
			text-align: center;
			border: 1px solid orangered;
		}
	}
}

::-webkit-scrollbar {
	width: 12px;
	height: 12px;
	background: #ffffff;
	border-radius: 6px;
}

::-webkit-scrollbar-thumb {
	background: #00a6ff;
	border-radius: 6px;
}
</style>

這邊我們的代碼里面定義了兩個插槽,default插槽是為了滿足element-ui中的下拉框長列表問題。

代碼如下:

<template>
	<div style="height: 100%">
		<div style="width: 240px; height: 100%">
			<el-select multiple v-model="activeName" @visible-change="visibleChange">
				<VirtualList v-if="visibleState" :data-list="data" :item-height="34" container-height="194px">
					<template #default="{ dataList }">
						<el-option v-for="i in dataList" :label="i.title" :value="i.id" :key="i.id" />
					</template>
				</VirtualList>
			</el-select>
		</div>
	</div>
</template>
<script lang="ts" setup>
import VirtualList from '@/components/VirtualList/index.vue'
import { reactive, ref } from 'vue'

const data = reactive<any[]>([])
for (let i = 0; i < 100000; i++) {
	data.push({ id: i, title: `標(biāo)題${i}` })
}
const activeName = ref('')

const visibleState = ref(false)

const visibleChange = (val: boolean) => {
	visibleState.value = val
}
</script>  

文章小尾巴

以上就是在Vue3中實現(xiàn)虛擬列表的方法示例的詳細(xì)內(nèi)容,更多關(guān)于Vue3虛擬列表的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue項目中使用vue-i18n報錯的解決方法

    vue項目中使用vue-i18n報錯的解決方法

    這篇文章主要給大家介紹了關(guān)于vue項目中使用vue-i18n報錯的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-01-01
  • Vue如何更改表格中的某一行選項值

    Vue如何更改表格中的某一行選項值

    這篇文章主要介紹了Vue如何更改表格中的某一行選項值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • 深入解析el-col-group強大且靈活的Element表格列組件

    深入解析el-col-group強大且靈活的Element表格列組件

    這篇文章主要為大家介紹了el-col-group強大且靈活的Element表格列組件深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • 詳解Vue 單文件組件的三種寫法

    詳解Vue 單文件組件的三種寫法

    這篇文章主要介紹了詳解Vue 單文件組件的三種寫法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • Vue-less的使用和deep深度選擇器詳解

    Vue-less的使用和deep深度選擇器詳解

    這篇文章主要介紹了Vue-less的使用和deep深度選擇器,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue第三方庫中存在擴展運算符報錯問題的解決方案

    vue第三方庫中存在擴展運算符報錯問題的解決方案

    這篇文章主要介紹了vue第三方庫中存在擴展運算符報錯問題,本文給大家分享解決方案,通過結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • Vue監(jiān)聽數(shù)組變化源碼解析

    Vue監(jiān)聽數(shù)組變化源碼解析

    這篇文章主要為大家詳細(xì)解析了Vue監(jiān)聽數(shù)組變化的源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • vue filter 完美時間日期格式的代碼

    vue filter 完美時間日期格式的代碼

    這篇文章主要介紹了vue filter 完美時間日期格式的方法,文中給大家提到了vue filter方法-時間格式化 的代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧
    2019-08-08
  • 如何使用Vue3實現(xiàn)文章內(nèi)容中多個"關(guān)鍵詞"標(biāo)記高亮顯示

    如何使用Vue3實現(xiàn)文章內(nèi)容中多個"關(guān)鍵詞"標(biāo)記高亮顯示

    高亮顯示是我們?nèi)粘i_發(fā)中經(jīng)常會遇到的需求,下面這篇文章主要給大家介紹了關(guān)于如何使用Vue3實現(xiàn)文章內(nèi)容中多個"關(guān)鍵詞"標(biāo)記高亮顯示的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-11-11
  • Vue開發(fā)中Jwt的使用詳解

    Vue開發(fā)中Jwt的使用詳解

    Vue中使用JWT進(jìn)行身份認(rèn)證也是一種常見的方式,它能夠更好地保護用戶的信息,本文主要介紹了Vue開發(fā)中Jwt的使用詳解,具有一定的參考價值,感興趣的可以了解一下
    2023-12-12

最新評論