前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >打造聊天框丝滑滚动体验:AI 聊天框的翻转之道

打造聊天框丝滑滚动体验:AI 聊天框的翻转之道

原创
作者头像
lrwlf
修改2023-11-29 14:45:57
1.3K5
修改2023-11-29 14:45:57
举报
文章被收录于专栏:lrwlf的xxx

逐字渲染的挑战

最近在开发AI聊天助手的时候,遇到了一个很有趣的滚动问题。我们需要开发一个类似微信聊天框的交互体验:

每当聊天框中展示新消息时,需要将聊天框滚动到底部,展示最新消息。

如果在 web 什么也不做,聊天体验可能是这样的,需要用户手动滚动到最新消息:

试想一下如何在 web 中实现微信的效果。每当聊天框中接收到新消息时,都需要调用滚动方法滚动到消息底部。

代码语言:javascript
复制
element.scrollIntoView({ behavior: "smooth", block: "end");

对于普通的聊天工具来说,这样实现没有什么大问题,因为聊天框接收到每条消息的长度都是确定的。但是 AI 大模型一般都是逐字渲染的,AI 助手聊天框接受的消息体大小不是固定的,而是会随着 AI 大模型的输出不断变大。如果仍使用 scrollIntoView 来滚动到底部,就需要监听消息体的变化,每次消息更新时都要通过 JavaScript 调用一次滚动方法,会造成一些问题:

  1. 频繁的 JavaScript 滚动调用。每输出一个文字要滚动一次,听起来就会性能焦虑。
  2. AI 正在输出内容时,用户无法滚动查看历史消息。用户向上滚动查看历史消息,会被 Javascript 不断执行的 scrollIntoView 打断。需要写特殊逻辑才能避免这个情况。
  3. 通过监听数据变化频繁的执行滚动,基于浏览器单线程的设计,不可避免的会造成滚动行为的滞后,导致聊天体验不够丝滑。

自然列表:灵感来源

聊天框接收到新消息时滚动到最新位置,总感觉这应该是一个很自然的行为,不需要这么多 Javascript 代码去实现滚动行为。

于是联想到了 Excel 表格,当我们在表格中第一行插入一行,这一行后边的内容会被很自然的挤下去。并不需要做什么滚动,这一行就会出现在最顶部的位置。

想到这里惊讶的发现,聊天框实际上不就是一个倒过来的列表吗? 列表最上边新增的行会把后边的行往下挤,而聊天框最下边新增消息需要把上边的消息往上挤。那假如我们将聊天框旋转 180° 呢...?

聊天框的翻转实现

翻转聊天框

利用 CSS transform: rotate(180deg) 将整个聊天框倒转,并且把接收到最新的消息插入到消息列表的头部。发现我们的设想确实是行得通的,新增的消息很自然的把历史消息顶了上去,消息卡片内容增加也能很自然的撑开。并且在消息输出时,也可以随意滚动查看历史记录。

滚动条调整与滚动行为反转

最核心的问题已经解决了,但总觉得哪里看起来怪怪的。滚动条怎么跑到左边,并且滚动行为和鼠标滚轮的方向反了,滚轮向上滚,聊天框却向下滚。(让人想起了 MacOS 连鼠标滚轮的反人类体验)

查阅文档发现 CSS 有个 direction: rtl; 属性可以改变内容的排布的方向。这样我们就可以把滚动条放回右边了。然后在通过监听滚动事件,改变滚动方向就可以恢复鼠标滚轮的滚动行为。

代码语言:javascript
复制
element.addEventListener('wheel', event => {
      event.preventDefault(); // 阻止默认滚动行为
      const { deltaY } = event; // 获取滚动方向和速度
      chatContent.current.scrollTop -= deltaY; //  反转方向
    });

消息卡片翻转恢复

可以看到目前就只剩下聊天框中的消息卡片是反的,接下来把聊天框中的消息卡片转正就大功告成了。我们在聊天框中,给每个消息卡片都添加 transform: rotate(180deg);direction: ltr; 样式,把消息重新转正。

这样就把翻转的行为全部隔离在了聊天框组件中。消息卡片组件完全感知不到自己其实已经被旋转了 180° 后又旋转了 180° 了。聊天框的父组件也完全不知道自己的子节点被转了又转。

总结

最后总结一下,我们通过两行 CSS 代码 + 反转滚动行为,利用浏览器的默认行为完美的实现了 AI 聊天框中的滚动体验。

代码语言:CSS
复制
transform: rotate(180deg);
direction: rtl;
代码语言:javascript
复制
element.addEventListener('wheel', event => {
      event.preventDefault(); // 阻止默认滚动行为
      const { deltaY } = event; // 获取滚动方向和速度
      chatContent.current.scrollTop -= deltaY; //  反转方向
    });

DEMO 仓库:https://github.com/lrwlf/message-scroll-demo


更新:

想到一个更简洁的办法可以达到相同的效果,只用把聊天框 CSS 设置为:

代码语言:css
复制
display: flex;
flex-direction: column-reverse;

让列表倒序渲染,并且像原来的方法一样,在消息列表的头部插入消息,就可以实现一样的效果。不需要对聊天框和消息体再进行旋转操作,也不需要反转滚动条的行为。

以上两种方法都存在一个相同的问题,当一开始聊天消息还很少时,聊天消息也会紧贴着底部,顶部会留出一片空白。

这时只需要在聊天列表的最开始设置一个空白的占位元素,把它的 CSS 设置为:

代码语言:txt
复制
flex-grow: 1;
flex-shrink: 1;

就可以实现消息少的时候自动撑开,把消息撑到顶部。消息列表开始滚动时,占位元素又会被挤压消失,不影响列表滚动效果。

(为了演示,把占位元素设置为了黑色)

更新部分代码见: https://github.com/lrwlf/message-scroll-demo

将 App.js 的 chat 组件,替换为 src/components/chat-flex

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 逐字渲染的挑战
  • 自然列表:灵感来源
  • 聊天框的翻转实现
    • 翻转聊天框
      • 滚动条调整与滚动行为反转
        • 消息卡片翻转恢复
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档