完整组件代码(chat.vue)​

<template>
  <view class="chat-container">
    <!-- 消息滚动区域 -->
    <scroll-view
      scroll-y
      :scroll-into-view="scrollToId"
      :style="{ height: scrollHeight + 'px' }"
      @scroll="handleScroll"
      scroll-with-animation
      class="message-scroll"
      :scroll-top="scrollTop"
    >
      <!-- 顶部加载提示 -->
      <view v-if="loadingHistory" class="loading-tip">
        <uni-load-more status="loading"></uni-load-more>
      </view>

      <!-- 消息列表 -->
      <view 
        v-for="(msg, index) in messages" 
        :id="'msg-' + index"
        :key="msg.id"
        class="message-item"
      >
        {{ msg.content }}
      </view>

      <!-- 底部锚点(关键元素) -->
      <view id="bottom-anchor"></view>
    </scroll-view>

    <!-- 输入框区域 -->
    <view class="input-area">
      <input 
        v-model="newMessage" 
        placeholder="输入消息..." 
        @confirm="sendMessage"
        focus
      />
      <button @click="sendMessage">发送</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      messages: [
        { id: 1, content: '第一条消息' },
        { id: 2, content: '第二条消息较长内容测试滚动效果' }
      ],
      newMessage: '',
      scrollToId: '',
      scrollHeight: 300,
      scrollTop: 0,
      loadingHistory: false,
      isAutoScroll: true,
      lastScrollTime: 0
    }
  },
  mounted() {
    this.calcScrollHeight();
    this.scrollToBottom();
    
    // 模拟接收消息
    setInterval(() => {
      this.receiveNewMessage();
    }, 3000);
  },
  methods: {
    // 发送新消息
    sendMessage() {
      if (!this.newMessage.trim()) return;
      
      this.messages.push({
        id: Date.now(),
        content: this.newMessage
      });
      this.newMessage = '';
    },
    
    // 模拟接收消息
    receiveNewMessage() {
      const randomMsg = '新消息 ' + Math.random().toString(36).substr(2, 5);
      this.messages.push({
        id: Date.now(),
        content: randomMsg
      });
    },
    
    // 计算滚动区域高度
    calcScrollHeight() {
      uni.getSystemInfo({
        success: (res) => {
          // 屏幕高度 - 输入框高度 - 其他固定元素高度
          this.scrollHeight = res.windowHeight - 100;
        }
      });
    },
    
    // 滚动到底部
    scrollToBottom() {
      if (!this.isAutoScroll) return;
      
      this.scrollToId = 'bottom-anchor';
      
      // 微信小程序需要强制更新
      // #ifdef MP-WEIXIN
      this.$forceUpdate();
      // #endif
      
      // 其他平台辅助刷新
      this.$nextTick(() => {
        this.scrollTop = Date.now();
      });
    },
    
    // 滚动事件处理
    handleScroll(e) {
      const now = Date.now();
      if (now - this.lastScrollTime < 100) return; // 节流
      this.lastScrollTime = now;
      
      const { scrollTop, scrollHeight } = e.detail;
      const threshold = 20; // 滚动到底部的阈值
      
      // 判断用户是否手动滚动了页面
      this.isAutoScroll = 
        scrollHeight - (scrollTop + this.scrollHeight) < threshold;
    },
    
    // 加载历史消息
    loadHistory() {
      if (this.loadingHistory) return;
      
      this.loadingHistory = true;
      setTimeout(() => {
        const newMsgs = Array(5).fill().map((_, i) => ({
          id: Date.now() + i,
          content: `历史消息 ${i}`
        }));
        this.messages.unshift(...newMsgs);
        this.loadingHistory = false;
      }, 1000);
    }
  },
  watch: {
    // 监听消息变化自动滚动
    messages: {
      handler() {
        this.$nextTick(this.scrollToBottom);
      },
      deep: true
    }
  },
  onPageScroll(e) {
    // 处理页面滚动到顶部加载历史
    if (e.scrollTop < 50 && !this.loadingHistory) {
      this.loadHistory();
    }
  }
}
</script>

<style>
.chat-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  padding: 10px;
  box-sizing: border-box;
}

.message-scroll {
  flex: 1;
  margin-bottom: 10px;
  border: 1px solid #eee;
  border-radius: 8px;
  overflow: hidden;
}

.message-item {
  padding: 12px;
  border-bottom: 1px solid #f5f5f5;
}

.input-area {
  display: flex;
  padding: 10px 0;
  border-top: 1px solid #eee;
}

.input-area input {
  flex: 1;
  border: 1px solid #ddd;
  padding: 8px 12px;
  border-radius: 4px;
  margin-right: 10px;
}

.input-area button {
  background: #007AFF;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 0 15px;
}

.loading-tip {
  padding: 10px;
  text-align: center;
  color: #999;
}

/* iOS弹性滚动优化 */
/* #ifdef H5 */
.message-scroll {
  -webkit-overflow-scrolling: touch;
}
/* #endif */
</style>

关键实现解析

  1. 动态高度计算

    • 通过 getSystemInfo 获取屏幕高度
    • 扣除输入框等固定区域高度
  2. 自动滚动机制

    • 监听 messages 变化自动触发 scrollToBottom
    • 使用 scroll-into-view 定位到底部锚点
    • 微信小程序需要 $forceUpdate 强制刷新
  3. 用户交互判断

    • 通过 @scroll 事件检测用户是否手动滚动
    • 设置阈值避免误判(20px)
  4. 性能优化

    • 滚动事件添加节流控制
    • 使用 $nextTick 确保 DOM 更新完成
    • 虚拟 ID 生成避免重复计算
  5. 多平台兼容

    • H5 使用 -webkit-overflow-scrolling
    • 小程序特殊处理
    • 全局样式隔离

使用说明

  1. 基础功能

    • 发送消息自动滚动到底部
    • 接收模拟消息自动展示
    • 上拉加载历史消息
  2. 扩展配置

    // 配置项示例
    data() {
      return {
        autoScroll: true,       // 是否启用自动滚动
        scrollThreshold: 20,    // 滚动到底部阈值(px)
        scrollDuration: 300     // 滚动动画时长(ms)
      }
    }
  3. 生产环境建议

    • 添加 WebSocket 消息监听
    • 实现消息分页加载
    • 加入敏感词过滤

测试验证

  1. 基础测试

    // 快速发送多条消息
    for (let i = 0; i < 20; i++) {
      this.sendMessage();
    }
  2. 边界测试

    • 超长单条消息(1000+字符)
    • 快速上下滑动时接收新消息
    • 横竖屏切换测试
  3. 真机验证

    • iOS 弹性滚动效果
    • 安卓低端机性能
    • 微信小程序真机表现

通过这套实现,可以确保在任何平台下都能实现:
✅ 新消息自动展示
✅ 用户手动滚动时不干扰
✅ 平滑的滚动体验
✅ 跨平台一致表现

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐