
通过scroll监听和虚拟列表技术实现,完整代码如下:
<template>
<view class="virtual-list">
<scroll-view
class="scroll-view"
scroll-y
:scroll-top="scrollTop"
:style="{ height: height + 'px' }"
@scroll="onScroll"
:scroll-with-animation="false"
>
<view class="loading" v-if="isLoading">加载中...</view>
<view class="list-content" :style="{ height: totalHeight + 'px' }">
<view
v-for="item in visibleData"
:key="item.id"
class="list-item"
:style="{ transform: `translateY(${item.top}px)`, position: 'absolute', width: '100%' }"
>
<view :class="['message', item.isSelf ? 'self' : 'other']">
<view class="avatar">{{item.isSelf ? '我' : '对方'}}</view>
<view class="content">{{item.content}}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
height: 0,
itemHeight: 80,
visibleCount: 0,
bufferCount: 5,
listData: [],
visibleData: [],
isLoading: false,
scrollTop: 0,
startIndex: 0,
loadMoreThreshold: 500,
lastLoadTime: 0,
loadCooldown: 1000,
totalHeight: 0,
lastScrollTop: 0,
scrollTimeout: null
}
},
async mounted() {
const sysInfo = uni.getSystemInfoSync()
this.height = sysInfo.windowHeight
this.visibleCount = Math.ceil(this.height / this.itemHeight) + this.bufferCount * 2
await this.initData()
},
methods: {
async initData() {
const initList = Array(30).fill(0).map((_, index) => ({
id: index,
content: `Message ${index}`,
isSelf: Math.random() > 0.5,
top: index * this.itemHeight
}))
this.listData = initList
this.totalHeight = this.listData.length * this.itemHeight
this.updateVisibleData()
},
updateVisibleData() {
const scrollTop = this.scrollTop
const start = Math.floor(scrollTop / this.itemHeight) - this.bufferCount
const end = start + this.visibleCount + this.bufferCount * 2
const startIndex = Math.max(0, start)
const endIndex = Math.min(this.listData.length, end)
this.visibleData = this.listData.slice(startIndex, endIndex).map(item => ({
...item,
top: item.top
}))
},
onScroll(e) {
const newScrollTop = e.detail.scrollTop
this.lastScrollTop = this.scrollTop
this.scrollTop = newScrollTop
// 使用防抖处理滚动更新
if(this.scrollTimeout) {
clearTimeout(this.scrollTimeout)
}
this.scrollTimeout = setTimeout(() => {
this.updateVisibleData()
this.checkNeedLoadMore(newScrollTop)
}, 300) // 约等于一帧的时间
},
async checkNeedLoadMore(scrollTop) {
if(scrollTop < this.loadMoreThreshold) {
const now = Date.now()
if(now - this.lastLoadTime > this.loadCooldown) {
await this.loadMore()
}
}
},
async loadMore() {
if(this.isLoading) return
this.isLoading = true
this.lastLoadTime = Date.now()
try {
const currentFirstItem = this.visibleData[0]
const oldOffset = currentFirstItem ? currentFirstItem.top : 0
await new Promise(resolve => setTimeout(resolve, 800))
const newMessages = Array(20).fill(0).map((_, index) => ({
id: `new_${this.listData.length + index}`,
content: `History Message ${this.listData.length + index}`,
isSelf: Math.random() > 0.5
}))
// 计算新消息的位置
const heightOffset = newMessages.length * this.itemHeight
// 更新现有消息的位置
this.listData = this.listData.map(item => ({
...item,
top: item.top + heightOffset
}))
// 添加新消息
const newMessagesWithPosition = newMessages.map((item, index) => ({
...item,
top: index * this.itemHeight
}))
// 合并数据
this.listData = [...newMessagesWithPosition, ...this.listData]
// 更新总高度
this.totalHeight = this.listData.length * this.itemHeight
// 调整滚动位置
const newScrollTop = oldOffset + heightOffset
this.scrollTop = newScrollTop
await this.$nextTick()
this.updateVisibleData()
} finally {
this.isLoading = false
}
}
}
}
</script>
<style lang="scss">
.virtual-list {
width: 100%;
.scroll-view {
width: 100%;
}
.list-content {
position: relative;
width: 100%;
}
.loading {
position: absolute;
top: 0;
left: 0;
right: 0;
text-align: center;
padding: 10px;
color: #999;
z-index: 1;
}
.list-item {
padding: 10px;
box-sizing: border-box;
.message {
display: flex;
align-items: flex-start;
margin: 10px 0;
&.self {
flex-direction: row-reverse;
.content {
margin: 0 10px 0 50px;
background: #007AFF;
color: #fff;
}
}
&.other {
.content {
margin: 0 50px 0 10px;
background: #f0f0f0;
}
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #ddd;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.content {
padding: 10px;
border-radius: 4px;
word-break: break-all;
}
}
}
}
</style>
网友回复
如何让ai帮我自动在小红书或抖音上自动根据需求截流与潜在客户聊天拉客?
如果用go编写一个在virtualbox中启动的简单操作系统?
go如何搭建一个零信任网络?
如何用python实现一个公网代理访问软件?
如何用go实现一个公网代理访问软件?
如何用python实现一个内网穿透打洞程序,实现内网的80端口暴露到公网上可以访问?
如何用go实现一个内网穿透打洞程序,实现内网的80端口暴露到公网上可以访问?
何为Shadowsocks 代理?
python如何实现类似php的opendir目录相互隔离的fastcgi多租户虚拟空间?
nodejs如何实现类似php的opendir目录相互隔离的fastcgi多租户虚拟空间?


