纯CSS实现AI Chat流式输出自动滚动到底部

5 阅读1分钟

现在的大模型聊天对话都有一个统一的交互:流式输出时,自动滚动到底部,如果用户手动滚动了就不再自动滚动到底部,这时如果手动滚到底部了也会再次继续触发自动滚动到底部。

那么是如何实现的呢?主流有两种方案:

  1. JavaScript 实现:监听 scroll 事件,要处理很多边界情况,挺麻烦的。
  2. CSS 实现:通过 flex-direction: column-reverse; 实现,简单快捷。

以下是用 CSS 实现的 demo

完整代码:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AI Chat 滚动</title>
    <style>
      * {
        box-sizing: border-box;
      }
      body {
        margin: 0;
        height: 100vh;
        display: flex;
        flex-direction: column;
      }
      .chater {
        flex: 1;
        overflow: auto;
        padding: 1em;
        display: flex;
        flex-direction: column-reverse;
      }
      .msger {
        flex: 1;
        display: flex;
        flex-direction: column;
      }
      .msger > pre {
        background-color: #eee;
        padding: 1em;
        border-radius: 8px;
        white-space: pre-wrap;
      }
      .msger .user {
        align-self: flex-end;
        margin-left: 10vw;
      }
      .msger .ai {
        align-self: flex-start;
        margin-right: 10vw;
      }
      .sender {
        flex-shrink: 0;
        padding: 1em;
      }
    </style>
  </head>
  <body>
    <div class="chater">
      <div class="msger"></div>
    </div>
    <div class="sender">
      <input type="text" />
      <button onclick="send()">发送</button>
    </div>
    <script>
      function send() {
        document.querySelector('.chater').scrollTop = document.querySelector('.chater').scrollHeight

        const pre = document.createElement('pre')
        pre.className = 'user'
        pre.textContent = document.querySelector('input').value
        document.querySelector('.msger').appendChild(pre)

        const pre2 = document.createElement('pre')
        pre2.className = 'ai'
        const timer = setInterval(() => {
          pre2.textContent +=
            String.fromCharCode(Math.floor(Math.random() * 122) + 65).repeat(
              Math.ceil(Math.random() * 10)
            ) + (Math.random() < 0.2 ? '\n' : ' ')
          if (pre2.textContent.length > 1000) clearInterval(timer)
        }, 50)
        document.querySelector('.msger').appendChild(pre2)
      }
    </script>
  </body>
</html>