+
98
-

回答

800_auto

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

网友回复

我知道答案,我要回答