可以使用虚拟列表只渲染可视区域,提供性能,我的代码如下:
<template>
<view class="container">
<scroll-view
ref="scrollView"
class="scroll-view"
scroll-y
@scroll="onScroll"
:scroll-top="scrollTop"
:scroll-with-animation="false"
:refresher-enabled="false"
style="height: 100vh;"
>
<view class="loading" v-if="isLoading">加载中...</view>
<!-- 用于撑开高度的容器 -->
<view :style="{ height: totalHeight + 'px' }">
<!-- 只渲染可视区域的消息 -->
<view
v-for="message in visibleMessages"
:key="message.id"
class="message-item"
:style="{
transform: `translateY(${message.top}px)`,
position: 'absolute',
width: 'calc(100% - 40px)'
}"
>
{{ message.content }}
</view>
</view>
</scroll-view>
</view>
</template>
<script>
function throttle(fn, delay) {
let timer = null;
return function(...args) {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
}
export default {
data() {
return {
allMessages: [], // 所有消息数据
visibleMessages: [], // 可视区域的消息
scrollTop: 0,
isLoading: false,
messageId: 0,
scrollQuery: null,
itemHeight: 60, // 每条消息的固定高度
bufferSize: 5, // 上下额外渲染的消息数量
viewportHeight: 0, // 可视区域高度
startIndex: 0, // 当前渲染的起始索引
endIndex: 0, // 当前渲染的结束索引
}
},
computed: {
totalHeight() {
return this.allMessages.length * this.itemHeight;
},
},
mounted() {
this.scrollQuery = uni.createSelectorQuery().in(this);
this.initViewport();
this.loadInitialMessages();
this.throttledScroll = throttle(this.handleScroll, 100);
},
beforeDestroy() {
this.scrollQuery = null;
},
methods: {
async initViewport() {
// 获取可视区域高度
const res = await new Promise(resolve => {
this.scrollQuery
.select('.scroll-view')
.boundingClientRect(data => {
resolve(data);
})
.exec();
});
this.viewportHeight = res.height;
this.updateVisibleMessages(0);
},
async loadInitialMessages() {
const initialMessages = await this.fetchMessages(30);
this.allMessages = initialMessages;
this.$nextTick(() => {
this.scrollToBottom();
this.updateVisibleMessages(this.scrollTop);
});
},
scrollToBottom() {
const height = this.totalHeight;
this.scrollTop = height;
this.updateVisibleMessages(this.scrollTop);
},
onScroll(e) {
this.throttledScroll(e);
},
async handleScroll(e) {
const scrollTop = e.detail.scrollTop;
// 距顶部100px时加载更多
if (scrollTop < 100 && !this.isLoading) {
await this.loadMoreMessages();
}
this.updateVisibleMessages(scrollTop);
},
updateVisibleMessages(scrollTop) {
// 计算可见区域的起始和结束索引
const startIndex = Math.floor(scrollTop / this.itemHeight);
const visibleCount = Math.ceil(this.viewportHeight / this.itemHeight);
const endIndex = startIndex + visibleCount;
// 添加缓冲区
this.startIndex = Math.max(0, startIndex - this.bufferSize);
this.endIndex = Math.min(this.allMessages.length, endIndex + this.bufferSize);
// 更新可见消息
this.visibleMessages = this.allMessages
.slice(this.startIndex, this.endIndex)
.map((msg, index) => ({
...msg,
top: (this.startIndex + index) * this.itemHeight
}));
},
async loadMoreMessages() {
if (this.isLoading) return;
try {
this.isLoading = true;
const oldScrollTop = this.scrollTop;
// 加载新消息
const newMessages = await this.fetchMessages(10);
this.allMessages = [...newMessages, ...this.allMessages];
// 调整滚动位置
await this.$nextTick();
this.scrollTop = oldScrollTop + (newMessages.length * this.itemHeight);
this.updateVisibleMessages(this.scrollTop);
} catch (error) {
console.error('加载消息失败:', error);
} finally {
this.isLoading = false;
}
},
async fetchMessages(count) {
return new Promise(resolve => {
setTimeout(() => {
const messages = Array.from({ length: count }, () => ({
id: `msg_${this.messageId++}`,
content: `历史消息 ${this.messageId}`
}));
resolve(messages);
}, 300);
});
}
}
}
</script>
<style>
.container {
height: 100vh;
}
.scroll-view {
padding: 20px;
box-sizing: border-box;
position: relative;
}
.message-item {
padding: 10px;
margin: 10px 0;
background: #f5f5f5;
border-radius: 5px;
box-sizing: border-box;
}
.loading {
text-align: center;
padding: 10px;
color: #999;
position: absolute;
width: 100%;
top: 0;
left: 0;
z-index: 1;
}
</style> 网友回复


