亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

引言

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

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

虛擬列表的基本原理

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

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

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

代碼實(shí)現(xiàn)

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

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

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

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

因?yàn)橄旅鏁?huì)通過滾動(dòng)條的高度去計(jì)算詳細(xì)的值。所以這里我們的起始下標(biāo)和結(jié)束下標(biāo)使用計(jì)算屬性去定義。代碼如下:

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

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

paddingAttr 的目的是保持列表的高度完整,并確保滾動(dòng)條能夠正常滾動(dòng)。由于實(shí)際渲染的 DOM 元素較少,可能導(dǎo)致滾動(dòng)條位置異常,因此需要通過設(shè)置 padding 來?yè)纹鹑萜鞯母叨?。此外,也可以使?transformposition 來實(shí)現(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}` })
}  
// 計(jì)算虛擬列表的padding(保持列表高度完整且滾動(dòng)條能正常滾動(dòng))
const paddingAttr = computed(() => {
	const paddingTop = start.value * itemHeight
	const paddingBottom = (dataList.length - over.value) * itemHeight
	return `${paddingTop}px 0 ${paddingBottom}px`
})
// 虛擬列表真實(shí)展示數(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、滾動(dòng)監(jiān)聽

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

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

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

// 虛擬列表真實(shí)展示數(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)聽滾動(dòng)條距離頂部距離,實(shí)時(shí)更新
const onScroll = () => {
	scrollTop.value = virtualContainer.value?.scrollTop ?? 0
} 
</script>

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

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

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

// 虛擬列表真實(shí)展示數(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'

/**
 * 虛擬列表的每一項(xiàng)的高度
 */
const itemHeight = 40

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

/**
 * 滾動(dòng)條距離頂部距離
 */
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í)展示數(shù)據(jù):起始下標(biāo)
 */
const start = computed(() => {
	const s = Math.floor(scrollTop.value / itemHeight)
	return Math.max(0, s)
})

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

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

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

/**
 * 監(jiān)聽滾動(dòng)條距離頂部距離,實(shí)時(shí)更新
 */
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>

示例:

組件封裝

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

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

<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 每一項(xiàng)的唯一標(biāo)識(shí)key
 * @param itemHeight 每一項(xiàng)的高度
 * @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%'
})

/**
 * 滾動(dòng)條距離頂部距離
 */
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í)展示數(shù)據(jù):起始下標(biāo)
 */
const start = computed(() => {
	const s = Math.floor(scrollTop.value / itemHeight - 5)
	return Math.max(0, s)
})

/**
 * 虛擬列表真實(shí)展示數(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)
})

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

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

/**
 * 監(jiān)聽滾動(dòng)條距離頂部距離,實(shí)時(shí)更新
 */
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>

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

代碼如下:

<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中實(shí)現(xiàn)虛擬列表的方法示例的詳細(xì)內(nèi)容,更多關(guān)于Vue3虛擬列表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue項(xiàng)目中使用vue-i18n報(bào)錯(cuò)的解決方法

    vue項(xiàng)目中使用vue-i18n報(bào)錯(cuò)的解決方法

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

    Vue如何更改表格中的某一行選項(xiàng)值

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

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

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

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

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

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

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

    vue第三方庫(kù)中存在擴(kuò)展運(yùn)算符報(bào)錯(cuò)問題的解決方案

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

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

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

    vue filter 完美時(shí)間日期格式的代碼

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

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

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

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

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

最新評(píng)論