Skip to content

虚拟列表 xVirtualList ​


预览

介绍 ​

万级列表用窗口切片,不要把一万个 list-item 全挂上。盒子定高,轨道撑总高,可见行 absolute + top;#item 只开一个出口;:scroll-top 只在跳转时写。默认 full 吃页面剩余高(页面级窗口高);抽屉、弹层写 :full="false" 跟父级局部高,不要量成窗口高。#top / #sticky 和轨道同一层 scroll-view,窗口用 outerScrollTop - topH - stickyH。不要原生子协商。App / Web #sticky 到达后 fixed。小程序 using-sticky + position:sticky。

平台兼容 ​

HarmonyH5andriodIOS小程序UTSUNIAPP-X SDKversion
☑☑☑️☑️☑️☑️4.76+1.1.18

文件路径 ​

ts

@/uni_modules/tmx-ui/components/x-virtual-list/x-virtual-list.uvue

使用 ​

ts

<x-virtual-list></x-virtual-list>

Props 属性 ​

名称说明类型默认值
list``
Array() : any[] => [] as any[]
rowKey``
string"key"
itemHeight``
string"50"
height``
string"100%"
full``
booleantrue
bufferSize``
number6
scrollIndex``
number-1
refresh``
xVirtualRefreshFun() : xVirtualRefreshFun => { return null }
pulldown``
xVirtualRefreshFun() : xVirtualRefreshFun => { return null }
showScrollbar``
booleanfalse

Events 事件 ​

名称参数说明
scroll--
itemClick--
refresh--
pulldown--
rangeChange--

Slots 插槽 ​

名称说明数据
top--
sticky--
empty--
item--
refresh--

Ref 方法 ​

名称参数返回值说明
scrollToIndex---
refreshSize---
resetScroll---
getRenderRange---

示例文件路径 ​

json

/pages/zhanshi/virtual-list

示例源码 ​

uvue
vue
<template>
	<!-- #ifdef MP-WEIXIN -->
	<page-meta :page-style="`background-color:${xThemeConfigBgColor}`">
		<navigation-bar :background-color="xThemeConfigNavBgColor"
			:front-color="xThemeConfigNavFontColor"></navigation-bar>
	</page-meta>
	<!-- #endif -->

	<view class="page">
		<x-virtual-list
			class="pageList"
			row-key="key"
			item-height="80"
			height="100%"
			:list="list"
			:scroll-index="jumpIndex"
			:pulldown="onPulldown"
			:refresh="onLoadMore"
		>
			<template #top>
				<x-sheet :margin="[0, 0, 0, 8]">
					<x-text font-size="18" class=" text-weight-b mb-8">虚拟列表 xVirtualList</x-text>
					<x-text color="#999999" class="mb-12">万级只渲染窗口行。向下先推顶部,到顶后滚下面的行并触底加载。#item 一次给出 rows,页面自己 v-for。本页 {{ list.length }} 条。</x-text>
					<view class="flex flex-row flex-row-center-between">
						<x-button size="small" class="flex-1" @click="jumpTo">指定</x-button>
						<x-button size="small" class="ml-5 flex-1" @click="appendRows">追加</x-button>
						<x-button size="small" class="mx-5 flex-1" @click="keepTwenty">20条</x-button>
						<x-button size="small" class="flex-1" @click="clearAll">清空</x-button>
					</view>
				</x-sheet>
			</template>
			<template #sticky="{ pinned }">
				<view :class="pinned ? 'tabFull' : 'tabCard'">
					<x-tabs
						v-model="menuId"
						:list="tabList"
						item-width="33.333%"
						height="44"
						:round="pinned ? '0' : '10'"
					></x-tabs>
				</view>
			</template>
			<template #item="{ rows }">
				<view
					v-for="row in rows"
					:key="keyOf(row)"
					class="rowHit"
					:style="styleOf(row)"
				>
					<view class="itemBox" :style="{ backgroundColor: itemBg }">
						<image class="round-6" style="width: 50px;height:50px;" :src="coverOf(dataOf(row))"></image>
						<x-text class="ml-10">{{ titleOf(dataOf(row)) }}</x-text>
					</view>
				</view>
			</template>
			<template #empty>
				<x-empty v-if="list.length == 0" :empty="true" :loading="false" btn-label="加载1万条" @click="reloadAll"></x-empty>
			</template>
		</x-virtual-list>
	</view>
</template>

<script lang="ts" setup>
	import { computed, nextTick, ref } from "vue"
	import { xConfig, TABS_ITEM_INFO } from "@/uni_modules/tmx-ui"

	type DemoRow = {
		key : string
		title : string
	}

	type RowBoxStyle = {
		position : string
		top : string
		height : string
		left : string
		width : string
	}

	function makeRows(from : number, count : number) : DemoRow[] {
		const out : DemoRow[] = []
		for (let i = 0; i < count; i++) {
			const n = from + i
			out.push({
				key: n.toString(),
				title: "数据项目-" + (n + 1).toString()
			})
		}
		return out
	}

	const jumpIndex = ref<number>(-1)
	const menuId = ref<string>("all")
	const tabList = [
		{ id: "all", title: "综合" },
		{ id: "new", title: "新品" },
		{ id: "hot", title: "热门" }
	] as TABS_ITEM_INFO[]
	const list = ref<DemoRow[]>(makeRows(0, 10000))

	const itemBg = computed(() : string => {
		if (xConfig.dark == "dark") return xConfig.sheetDarkColor
		return "#ffffff"
	})

	function keyOf(row : any) : string {
		return "" + row.key
	}

	function dataOf(row : any) : any {
		return row.item
	}

	function styleOf(row : any) : RowBoxStyle {
		const s = row.style
		if (s != null) {
			return {
				position: "" + s.position,
				top: "" + s.top,
				height: "" + s.height,
				left: "" + s.left,
				width: "" + s.width
			}
		}
		const top = row.top
		let y = 0
		if (typeof top == "number") y = Math.round(top)
		return {
			position: "absolute",
			top: y.toString() + "px",
			height: "80px",
			left: "0px",
			width: "100%"
		}
	}

	function coverOf(item : any) : string {
		return "https://picsum.photos/100/100?random=" + item.key
	}

	function titleOf(item : any) : string {
		const t = item.title
		if (t == null) return ""
		return "" + t
	}

	function wait(ms : number) : Promise<void> {
		return new Promise<void>((resolve) => {
			setTimeout(() => {
				resolve()
			}, ms)
		})
	}

	function reloadAll() : void {
		list.value = makeRows(0, 10000)
		jumpIndex.value = 0
	}

	function appendRows() : void {
		list.value = list.value.concat(makeRows(list.value.length, 20))
	}

	function keepTwenty() : void {
		list.value = list.value.slice(0, 20)
	}

	function clearAll() : void {
		list.value = [] as DemoRow[]
	}

	function jumpTo() : void {
		if (jumpIndex.value == 2000) {
			jumpIndex.value = -1
			nextTick(() : void => {
				jumpIndex.value = 2000
			})
			return
		}
		jumpIndex.value = 2000
	}

	async function onPulldown() : Promise<void> {
		await wait(800)
		list.value = makeRows(0, 10000)
	}

	async function onLoadMore() : Promise<void> {
		await wait(600)
		appendRows()
	}
</script>

<style scoped>
	.page {
		flex: 1;
		display: flex;
		flex-direction: column;
	}

	.pageList {
		flex: 1;
		min-height: 0;
	}

	.tabCard {
		margin: 0 8px 8px 8px;
		border-radius: 10px;
		overflow: hidden;
	}

	.tabFull {
		margin: 0;
	}

	.rowHit {
		box-sizing: border-box;
		padding-bottom: 8px;
		overflow: hidden;
	}

	.itemBox {
		height: 72px;
		display: flex;
		flex-direction: row;
		align-items: center;
		padding: 0 12px;
		border-radius: 10px;
	}
</style>
最近更新