加载新消息,滚动条位置不变

在实现聊天记录的“加载更多”功能时,保持滚动条位置不变是一个常见的需求。这个功能的核心是在加载新消息后,确保用户的视角没有被突兀地改变。以下是实现这一功能的常用方法:

1. 计算并调整滚动条的位置

js,web端加载新消息,滚动条位置不变,最新消息渲染至视口_加载

首先,在加载新消息前,计算当前滚动条的位置oldScrollTop,然后在新消息加载完成后,计算滚动条的偏移量scrollOffset,通过赋值调整滚动条位置来恢复用户的视角。

  • 代码描述
const chatContainer = document.getElementById('chat-container');//window。聊天窗口

function loadMoreMessages() {
  // 获取当前滚动条的偏移量
  const oldScrollHeight = chatContainer.scrollHeight;
  const oldScrollTop = chatContainer.scrollTop;
  // 加载更多消息
  // 模拟异步加载
  setTimeout(() => {
    // 这里插入新消息到DOM中
    // chatContainer.innerHTML = newMessages + chatContainer.innerHTML;

    // 计算新消息加载后增加的高度
    const newScrollHeight = chatContainer.scrollHeight;
    const scrollOffset = newScrollHeight - oldScrollHeight;

    // 恢复滚动条到原来的位置
    chatContainer.scrollTop = oldScrollTop + scrollOffset;
  }, 500);
}

// 监听滚动事件,用户滑到顶端时触发加载更多消息
chatContainer.addEventListener('scroll', () => {
  if (chatContainer.scrollTop === 0) {
    loadMoreMessages();
  }
});

需要注意的是异步渲染的问题。如果是在vue中就需要使用this.$nextTick(),来计算渲染完成后的scrollHeight 例如:

setTimeout(()=>{
        this.ChatList.unshift({
          "id": 4,
          "SessionDateTime": 1724822895499,
          "SessionImage": "",
          "SessionText": "你好",
          "ResponseDateTime": 1724822895499,
          "ResponseText": "你好!",
          "State": 0
          },{
          "id": 5,
          "SessionDateTime": 1724822895499,
          "SessionImage": "",
          "SessionText": "最近怎么样",
          "ResponseDateTime": 1724822895499,
          "ResponseText": "还好,你呢",
          "State": 0
          },{
          "id": 6,
          "SessionDateTime": 1724822895499,
          "SessionImage": "",
          "SessionText": "还行吧,一如既往",
          "ResponseDateTime": 1724822895499,
          "ResponseText": "下周我要去旅游,一起不",
          "State": 0
          })
          this.$nextTick(() => {
            const newScrollHeight = bodyDom.scrollHeight;
            const scrollOffset = newScrollHeight - this.oldScrollHeight;
            //计算赋值新的scrollTop;达到视口内容保持一致的效果。
            bodyDom.scrollTop = this.oldScrollTop + scrollOffset;
          })
      },5000)

//滚动事件
 handleScroll($event){
    const { scrollHeight, scrollTop, clientHeight } = $event.target;
    this.oldScrollHeight = scrollHeight;
    this.oldScrollTop = scrollTop;
  }

2. 使用虚拟滚动技术

对于大量聊天记录的加载,虚拟滚动是一个性能优化的选择。你可以使用像 react-windowreact-virtualized 这样的库来只渲染可见部分的消息,并且在用户滚动时动态加载内容。

3. CSS 滚动行为优化

在某些情况下,可以使用 scroll-behavior 来确保滚动平滑:

#chat-container {
  scroll-behavior: smooth;
}

不过,这个通常适用于让滚动变得更平滑,对于你提到的“加载更多”场景,还需要手动调整滚动条位置。

4. 最新消息渲染至视口

scrollIntoView 是一个非常实用的 DOM 方法,它的主要功能是让某个元素滚动到视图的可见区域。这个方法在各种需要确保某个元素可见的场景下都能派上用场,比如自动滚动到某个错误消息、定位到某个被点击的项目、或者在聊天窗口中滚动到最新的一条消息。

使用场景

  1. 自动滚动到错误提示
  • 表单提交后,如果有验证错误,可以自动滚动到第一个错误提示。
  1. 导航链接
  • 单页应用(SPA)中,点击导航链接后,可以平滑地滚动到相应的内容区域。
  1. 聊天应用
  • 加载新消息后,自动滚动到最新的消息位置。
  1. 固定头部或底部的元素
  • 确保在滚动操作中某个固定位置的元素保持可见。

scrollIntoView 的基本用法

语法:

element.scrollIntoView(alignToTop); // 简单用法
element.scrollIntoView(options);    // 更详细的配置
  • alignToTop (可选):
  • true(默认):元素的顶部将与其滚动容器的可见区域顶部对齐。
  • false:元素的底部将与其滚动容器的可见区域底部对齐。
  • options (可选):这是一个包含更多选项的对象,用于控制滚动行为。
element.scrollIntoView({
  behavior: 'smooth', // 'auto' or 'smooth'
  block: 'start',     // 'start', 'center', 'end', or 'nearest'
  inline: 'nearest'   // 'start', 'center', 'end', or 'nearest'
});

示例

  1. 滚动到元素顶部
document.getElementById('targetElement').scrollIntoView(true);
  1. 滚动到元素底部
document.getElementById('targetElement').scrollIntoView(false);
  1. 平滑滚动到元素
document.getElementById('targetElement').scrollIntoView({
  behavior: 'smooth',
  block: 'start'
});

注意事项

  • 浏览器支持scrollIntoView 是一个标准的浏览器 API,大多数现代浏览器都支持,但要确保在某些老旧浏览器中进行兼容性处理。
  • 性能:当涉及大量内容或复杂的页面布局时,频繁调用 scrollIntoView 可能会影响性能,尤其是在低端设备上。对于这种情况,可以考虑优化调用频率或者使用虚拟滚动技术。

结论

scrollIntoView 非常适合那些需要在用户操作或页面事件后,将某个元素滚动到可视区域的场景。通过合理配置滚动行为和对齐方式,可以显著提升用户体验。但是我这里使用平滑过渡到滚动元素时,无法真正达到底部对齐,需要手动再滑动一下,因为业务效果不要求,就没具体研究,知道的好心人可以提点建议

总结

保持滚动条位置不变的关键是记录和恢复滚动条的高度差异。通过计算加载新消息前后的高度差,并调整滚动位置,可以有效地防止用户视角的跳动。