封装虚拟列表组件
TIP
封装的虚拟列表只支持固定高度的列表(每一项的高度固定)。封装时使用了作用域插槽,所有uni-app 小程序端是不支持的
vue-virtual-scroller插件
TIP
其他人封装的虚拟列表插件,支持高度不固定的列表
uni-app版本
vue
<!--
* @Author: 李金深 2896583081@qq.com
* @Date: 2023-05-16 09:59:08
* @LastEditors: 李金深 2896583081@qq.com
* @LastEditTime: 2023-05-16 15:17:54
* @FilePath: /com.huizhouyiren.APP.d0101/src/components/virtualList/virtualList.vue
* @Description: 虚拟列表组件
-->
<template>
<scroll-view scroll-y="true" :style="{ height: contentHeight + 'px' }" @scroll="scrollEvent">
<view class="relative" :style="{ height: scrollHeight + 'px' }">
<view :style="{ 'top': `${top}px` }" style="width: 100%;position:absolute" class="left-0 padding_box">
<view class="flex items-center w-full border_bottom" v-for="(item, index) in showList"
:key="item.id + '' + index" :style="{ height: itemHeight + 'px' }">
<slot :item="item"></slot>
</view>
</view>
</view>
</scroll-view>
</template>
<script>
export default {
props: {
contentHeight: { // 可视区域高度
require: true,
default: 0
},
itemHeight: { // 每条数据的高度
require: true,
default: 100
},
showNum: { //每次加载到可视区域的数量,itemHeight X showNum 要可视区域高度 ,否则页面滚动不了。
require: true,
default: 12
},
sourceData: { // 源数据数组
require: true,
default: [],
}
},
data() {
return {
showList: [], // 可视区域显示的数据
top: 0, //偏移量
scrollTop: 0, //卷起的高度
startIndex: 0, //可视区域第一条数据的索引
endIndex: 0, //可视区域最后一条数据后面那条数据的的索引,因为后面要用slice(start,end)方法取需要的数据,但是slice规定end对应数据不包含在里面
scrollHeight: [], // 滚动的内容高度
}
},
created() {
if (this.showNum * this.itemHeight <= this.contentHeight) {
uni.showToast({
title: "虚拟列表可视区域中显示的数据条数太少,列表无法滚动",
icon: "none",
duration: 3000
})
}
},
watch: {
sourceData: {
handler: function () {
this.scrollHeight = this.sourceData.length * this.itemHeight
this.getShowList()
},
immediate: true,
deep: true
}
},
methods: {
/**
* @description: 计算可视区域数据
* @return {*}
*/
getShowList() {
this.startIndex = Math.floor(this.scrollTop / this.itemHeight); //可视区域第一条数据的索引
this.endIndex = this.startIndex + this.showNum; //可视区域最后一条数据的后面那条数据的索引
this.showList = this.sourceData.slice(this.startIndex, this
.endIndex) //可视区域显示的数据,即最后要渲染的数据。实际的数据索引是从this.startIndex到this.endIndex-1
this.top = this.scrollTop - (this.scrollTop % this
.itemHeight); //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量,这样随机滑动时第一条数据都是完整显示的
},
/**
* @description: 区域滚动事件
* @param {*} e
* @return {*}
*/
scrollEvent(e) {
this.$u.throttle(() => {
this.scrollTop = e.detail.scrollTop
this.getShowList()
}, 50)
},
/**
* @description: 设置属性值 top scrollTop等 供父组件使用
* @param {*} attribute 属性对象 如 {top:1,scrollTop:10}
* @return {*}
*/
setAttribute(attribute) {
Object.keys(attribute).forEach(key => {
if (Reflect.has(this, key)) {
this[key] = attribute[key]
}
})
}
}
}
</script>
vue
<!--使用-->
<virtual-list ref="chatVirtual" :contentHeight="contentHeight" :itemHeight="102" :sourceData="chat.list">
<template #default="{ item }">
<view class="w-full">
<!--代码逻辑-->
</view>
</template>
</virtual-list>
vue3版本
vue
<!--
* @Author: 李金深 2896583081@qq.com
* @Date: 2023-06-25 10:33:19
* @LastEditors: 李金深 2896583081@qq.com
* @LastEditTime: 2023-06-27 14:09:35
* @FilePath: /com.huizhouyiren.web.d0101/src/components/virtualList/virtualList.vue
* @Description: 虚拟列表组件
-->
<script setup>
import { ref, watch, onMounted } from "vue"
import { throttle } from '@/utils/tools'
const props = defineProps({
itemHeight: { // 每条数据的高度
require: true,
default: 100
},
showNum: { //每次加载到可视区域的数量,itemHeight X showNum 要可视区域高度 ,否则页面滚动不了。
require: true,
default: 12
},
sourceData: { // 源数据数组
require: true,
default: [],
}
})
let scrollBox = ref(null)
let showList = ref([]) // 可视区域显示的数据
let top = ref(0) //偏移量
let scrollTop = ref(0) //卷起的高度
let startIndex = ref(0)//可视区域第一条数据的索引
let endIndex = ref(0) //可视区域最后一条数据后面那条数据的的索引,因为后面要用slice(start,end)方法取需要的数据,但是slice规定end对应数据不包含在里面
let scrollHeight = ref([]) // 滚动的内容高度
/**
* @description: 计算可视区域数据
* @return {*}
*/
const getShowList = () => {
startIndex.value = Math.floor(scrollTop.value / props.itemHeight); //可视区域第一条数据的索引
endIndex.value = startIndex.value + props.showNum; //可视区域最后一条数据的后面那条数据的索引
showList.value = props.sourceData.slice(startIndex.value, endIndex.value) //可视区域显示的数据,即最后要渲染的数据。实际的数据索引是从this.startIndex到this.endIndex-1
top.value = scrollTop.value - (
scrollTop.value % props.itemHeight); //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量,这样随机滑动时第一条数据都是完整显示的
}
/**
* @description: 设置滚动高度
* @param {*} scrollTopValue
* @return {*}
*/
const setTop = (scrollTopValue) => {
scrollTop.value = scrollTopValue
getShowList()
}
defineExpose({ setTop })
watch(() => { return props.sourceData }, () => {
scrollHeight.value = props.sourceData.length * props.itemHeight
getShowList()
}, {
deep: true,
immediate: true
})
onMounted(() => {
scrollBox.value.addEventListener('scroll', (e) => {
// throttle 节流函数
throttle(() => {
scrollTop.value = e.target.scrollTop
getShowList()
}, 50)
})
})
</script>
<template>
<div style="height: 100%;width: 100%;">
<div class="relative scroll_box" style="height: 100%;width: 100%;overflow-y: scroll;" ref="scrollBox">
<div :style="{ 'top': `${top}px` }" style="width: 100%;position:absolute" class="left-0">
<div class="flex items-center w-full" v-for="(item, index) in showList" :key="item.id + '' + index"
:style="{ height: itemHeight + 'px' }">
<slot :item="item"></slot>
</div>
</div>
</div>
</div>
</template>
<style>
.scroll_box::-webkit-scrollbar {
height: 0;
width: 0;
color: transparent;
}
</style>
vue
<!--使用-->
<VirtualList ref="friendVirtualList" :itemHeight="friendItemHeight" :showNum="friendNum"
:sourceData="chatStore.friend.list" v-show="chatStore.friend.list.length > 0">
<template #default="data">
<div :style="{ height: friendItemHeight + 'px' }" class="flex items-center w-full"
@click="selectUser(data.item)">
<div class="flex items-start w-full px-4 cursor-pointer" style="height:2.5rem;">
<div class="relative flex items-center pr-2">
<CustomImage width="2.5rem" roundedClass="rounded-md" :src="data.item.avatar"
bgColor="#323232">
</CustomImage>
</div>
<div class="flex items-center justify-between flex-1 h-full pr-2">
<div class="flex-1 mr-2 text-sm title_desc">{{ data.item.nickname }}</div>
<SelectBox width="18px" v-if="multiple" v-model:select="data.item.isSelect"
:id="data.item.id"></SelectBox>
</div>
</div>
</div>
</template>
</VirtualList>