现在的大模型聊天对话都有一个统一的交互:流式输出时,自动滚动到底部,如果用户手动滚动了就不再自动滚动到底部,这时如果手动滚到底部了也会再次继续触发自动滚动到底部。
那么是如何实现的呢?主流有两种方案:
- JavaScript 实现:监听 scroll 事件,要处理很多边界情况,挺麻烦的。
- CSS 实现:通过
flex-direction: column-reverse;实现,简单快捷。
以下是用 CSS 实现的 demo
核心代码:
.chater {
overflow: auto;
display: flex;
flex-direction: column-reverse;
}
.msger {
flex: 1;
display: flex;
flex-direction: column;
}
完整代码:
<!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;
}
textarea {
width: 100%;
}
</style>
</head>
<body>
<div class="chater">
<div class="msger"></div>
</div>
<div class="sender">
<textarea rows="6" placeholder="Enter发送,Shift+Enter换行" enterkeyhint="send"></textarea>
</div>
<script>
const textarea = document.querySelector('textarea')
const chater = document.querySelector('.chater')
const msger = document.querySelector('.msger')
textarea.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
send()
}
})
function send() {
chater.scrollTop = chater.scrollHeight
const pre = document.createElement('pre')
pre.className = 'user'
pre.textContent = textarea.value
textarea.value = ''
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)
msger.appendChild(pre2)
}
</script>
</body>
</html>
还有一个细节,输入框支持Enter发送,Shift+Enter换行
textarea.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
send()
}
})
在 Vue 中实现更简洁:
<textarea
placeholder="请输入,Enter发送,Shift+Enter换行"
@keydown.enter.exact.prevent="send"
@keydown.enter.shift="() => {}"
></textarea>
.enter.exact 表示仅按下 Enter