+
91
-

回答

可以使用虚拟列表只渲染可视区域,提供性能,我的代码如下:

<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>

网友回复

我知道答案,我要回答