聊天机器人 : Auto Scroll 聊天窗口自动滚动

一、前言

在之前的开发中,我们已经完成了聊天消息的渲染、Markdown 渲染以及代码高亮。

但是,当聊天记录越来越长时,会出现一个新的问题:

当 AI 生成新的回复时,聊天窗口不会自动移动到最新消息的位置。

用户可能需要手动滚动页面才能看到 AI 正在生成的内容,这会影响聊天体验。

因此,今天我们来学习 Auto Scroll,也就是聊天窗口的自动滚动功能。

二、理解三个核心属性

在实现自动滚动之前,需要先理解 JavaScript 中与滚动相关的三个属性。

1. scrollTop

scrollTop 表示元素内容向上滚动的距离。

例如,聊天窗口向下滚动了 300px:

console.log(chatBox.scrollTop);

此时,返回值可能是 300。

我们也可以通过修改它来控制滚动位置:

chatBox.scrollTop = 300;

2. scrollHeight

scrollHeight 表示元素内容的整体高度,包括当前可视区域之外的内容。

例如,聊天窗口的可视区域只有 500px,但聊天记录已经有 2000px 高,那么 scrollHeight 就可能接近 2000。

console.log(chatBox.scrollHeight);

3. clientHeight

clientHeight 表示元素可视区域的高度。

console.log(chatBox.clientHeight);

假设聊天窗口的可视区域高度为 500px,那么 clientHeight 就是 500。

4. 三个属性之间的关系

可以简单理解为:

属性含义
scrollTop已经向下滚动了多少
scrollHeight内容总共有多高
clientHeight当前能看到的区域有多高

当用户滚动到最底部时,通常满足:

scrollTop + clientHeight >= scrollHeight

因此,我们可以利用这些属性判断用户是否已经接近聊天窗口的底部。

三、实现基础自动滚动

理解了这三个属性后,实现自动滚动就很简单了。

假设我们的聊天窗口对应一个 DOM 元素:

const chatBox = document.getElementById("chat-box");

当新增一条消息后,只需要执行:

chatBox.scrollTop = chatBox.scrollHeight;

这行代码的含义是:

将当前滚动位置设置为内容的整体高度,使聊天窗口滚动到最新内容的位置。

也可以使用 scrollTo():

chatBox.scrollTo({
    top: chatBox.scrollHeight,
    behavior: "smooth"
});

其中:

  • top:目标滚动位置。
  • behavior: "smooth":使用平滑滚动效果。

需要注意,自动滚动应该发生在新消息加入 DOM 之后,否则计算到的 scrollHeight 可能还是更新之前的高度。

四、Streaming 场景下的自动滚动

在普通聊天中,一条消息通常一次性显示出来。

但在 KK Chat 中,我们使用了 Streaming,让 AI 的回复逐步输出。

例如:

你好
你好!很高兴
你好!很高兴认识你。

随着新内容不断追加,消息区域的高度也可能持续变化。

如果只在创建消息时滚动一次,那么当 AI 继续生成长文本时,用户仍然可能看不到最新内容。

因此,我们需要在 Streaming 更新消息内容时,也执行滚动处理。

1. 基础实现

假设我们已经创建了 AI 回复对应的消息元素:

function updateAssistantMessage(messageElement, content) {
    messageElement.textContent = content;

    chatBox.scrollTop = chatBox.scrollHeight;
}

每次收到新的内容后,更新消息并滚动到底部。

这里的 messageElement 是 AI 回复对应的 DOM 元素,content 是当前要显示的文本。

这只是一个简化示例,实际项目中还需要根据自己的 Streaming 逻辑决定如何追加文本,以及何时重新渲染 Markdown。

2. 为什么不能始终强制滚动?

假设 AI 正在生成一篇很长的回答。

用户为了查看上面的内容,手动向上滚动了一段距离。

此时,如果每收到一个新的 Streaming 数据片段,就强制执行:

chatBox.scrollTop = chatBox.scrollHeight;

聊天窗口就会立刻跳回底部。

用户无法继续阅读之前的内容。

因此,更合理的方案是:

只有当用户原本就在底部附近时,才自动跟随新内容滚动。

3. 优化:判断用户是否接近底部

首先,编写一个判断函数:

function isNearBottom(element) {
    return (
        element.scrollHeight -
        element.scrollTop -
        element.clientHeight
    ) < 80;
}

这里的 80 表示容许的距离阈值,单位是 CSS 像素。

如果距离底部不足 80px,就认为用户仍然希望跟随最新消息。

接下来,在更新消息之前记录用户的位置:

function updateAssistantMessage(messageElement, content) {
    const shouldAutoScroll = isNearBottom(chatBox);

    messageElement.textContent = content;

    if (shouldAutoScroll) {
        chatBox.scrollTop = chatBox.scrollHeight;
    }
}

这里有一个关键细节:

必须在更新消息内容之前判断用户是否接近底部。

因为更新内容后,scrollHeight 可能已经发生变化。如果在更新之后才判断,可能无法准确反映用户更新前的位置。

通过这种方式,我们就能兼顾两种情况:

  • 用户正在底部查看最新回复:自动跟随。
  • 用户主动向上阅读历史内容:不强制跳回底部。

五、实践中需要注意的问题

1. 为什么设置 scrollTop 后没有滚动?

首先检查三个方面:

  • chatBox 是否正确获取了 DOM 元素。
  • 聊天窗口是否真的存在可滚动的内容。
  • CSS 是否正确设置了聊天区域的高度和 overflow-y。

例如:

#chat-box {
    height: 500px;
    overflow-y: auto;
}

如果元素没有形成独立的滚动区域,实际滚动的可能是整个页面,而不是聊天窗口本身。

2. 为什么 Streaming 时滚动不够及时?

因为 Streaming 会持续更新消息内容,内容高度也可能随之改变。

因此,自动滚动逻辑需要与消息更新过程配合,而不是只在用户发送消息时执行一次。

对于高频更新的场景,还可以使用 requestAnimationFrame() 合并滚动操作,避免在短时间内重复执行过多次滚动。但在基础版本中,可以先使用简单实现。

3. 为什么不能无条件滚动到底部?

因为自动滚动不仅要考虑程序的行为,也要考虑用户当前的操作。

如果用户正在阅读历史消息,程序不断强制滚动到底部,就会打断阅读。

因此,自动滚动的核心并不是每次都滚到底,而是判断用户是否仍然希望跟随最新内容。

六、总结

今天我们学习了 Auto Scroll 的基本原理与实现方式。

主要知识点包括:

  1. scrollTop:当前滚动位置。
  2. scrollHeight:内容整体高度。
  3. clientHeight:可视区域高度。
  4. 使用 scrollTop = scrollHeight 实现基础自动滚动。
  5. 在 Streaming 更新消息时同步处理滚动。
  6. 通过判断用户是否接近底部,避免打断用户阅读历史消息。

Auto Scroll 看起来只是一个小功能,但它体现了前端开发中一个重要的思路:

功能实现只是第一步,还需要考虑用户在真实使用场景中的体验。

对于 KK Chat 来说,消息渲染、Streaming、Markdown、代码高亮和自动滚动共同组成了一个完整的聊天交互流程。

下一步,可以继续优化聊天界面的交互体验,例如处理代码块渲染后的滚动、长消息布局,以及用户手动滚动时的行为。


KJ

项目:KK Chat

系列:《从零开发 AI Chatbot》

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐