可以使用虚拟列表只渲染可视区域,提供性能,我的代码如下:
<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>
网友回复