feat: 智能滚动 - 流式回复时可向上查看历史,浮动按钮回到底部
- 流式输出时不再强制锁定底部,用户可自由滚动查看历史 - 滚动离开底部时显示「回到底部」浮动按钮(sticky 定位) - 点击按钮平滑滚动并恢复自动跟踪 - 用户手动滚回底部时自动恢复跟踪 - 发送消息/新建会话时自动重置为跟踪状态 - 首次渲染(加载历史)强制滚到底部
This commit is contained in:
@@ -39,10 +39,38 @@ function getFileIcon(filename) {
|
||||
return map[ext] || '📄';
|
||||
}
|
||||
|
||||
let chatAreaEl, messagesContainerEl, emptyStateEl, scrollBtnEl;
|
||||
let autoScroll = true; // 是否自动滚动到底部
|
||||
|
||||
export function initChatArea() {
|
||||
chatAreaEl = document.querySelector('#chatArea');
|
||||
messagesContainerEl = document.querySelector('#messagesContainer');
|
||||
emptyStateEl = document.querySelector('#emptyState');
|
||||
scrollBtnEl = document.querySelector('#scrollToBottom');
|
||||
|
||||
// 监听用户手动滚动
|
||||
chatAreaEl.addEventListener('scroll', () => {
|
||||
const atBottom = isAtBottom();
|
||||
if (atBottom) {
|
||||
autoScroll = true;
|
||||
scrollBtnEl.style.display = 'none';
|
||||
} else {
|
||||
autoScroll = false;
|
||||
scrollBtnEl.style.display = '';
|
||||
}
|
||||
});
|
||||
|
||||
// 点击滚动到底部按钮
|
||||
scrollBtnEl.addEventListener('click', () => {
|
||||
autoScroll = true;
|
||||
scrollBtnEl.style.display = 'none';
|
||||
chatAreaEl.scrollTo({ top: chatAreaEl.scrollHeight, behavior: 'smooth' });
|
||||
});
|
||||
}
|
||||
|
||||
/** 判断是否在底部附近(60px 容差) */
|
||||
function isAtBottom() {
|
||||
return chatAreaEl.scrollHeight - chatAreaEl.scrollTop - chatAreaEl.clientHeight < 60;
|
||||
}
|
||||
|
||||
/** 安全地将 Markdown 转为 HTML */
|
||||
@@ -58,8 +86,9 @@ export function safeMarkdown(text) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 滚动到底部 */
|
||||
/** 滚动到底部(仅当 autoScroll 开启时) */
|
||||
export function scrollToBottom() {
|
||||
if (!autoScroll) return;
|
||||
requestAnimationFrame(() => {
|
||||
chatAreaEl.scrollTop = chatAreaEl.scrollHeight;
|
||||
});
|
||||
@@ -80,11 +109,17 @@ export function renderMessages() {
|
||||
messagesContainerEl.style.display = '';
|
||||
|
||||
const existingCount = messagesContainerEl.children.length;
|
||||
const isNewMessage = existingCount > 0 && existingCount < msgs.length;
|
||||
for (let i = existingCount; i < msgs.length; i++) {
|
||||
appendMessageDOM(msgs[i], i);
|
||||
}
|
||||
|
||||
scrollToBottom();
|
||||
// 新消息按 autoScroll 决定;首次渲染(加载历史)强制滚到底部
|
||||
if (isNewMessage) {
|
||||
scrollToBottom();
|
||||
} else if (msgs.length > 0) {
|
||||
chatAreaEl.scrollTop = chatAreaEl.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/** 追加单条消息 DOM */
|
||||
@@ -274,6 +309,12 @@ export function clearMessages() {
|
||||
messagesContainerEl.innerHTML = '';
|
||||
}
|
||||
|
||||
/** 重置自动滚动(用户发送消息、新建会话时调用) */
|
||||
export function enableAutoScroll() {
|
||||
autoScroll = true;
|
||||
if (scrollBtnEl) scrollBtnEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// ── 导出功能 ──
|
||||
|
||||
/** 导出为 Markdown */
|
||||
|
||||
@@ -8,7 +8,7 @@ import { fileToBase64, fileToText, detectLanguage, debounce, truncate, escapeHtm
|
||||
import { getSelectedModel, isThinkEnabled, isVisionAvailable } from './model-bar.js';
|
||||
import {
|
||||
renderMessages, appendAssistantPlaceholder, appendSystemMessage,
|
||||
updateLastAssistantMessage, clearMessages, safeMarkdown
|
||||
updateLastAssistantMessage, clearMessages, safeMarkdown, enableAutoScroll
|
||||
} from './chat-area.js';
|
||||
import { showToast } from './toast.js';
|
||||
import { checkConnection } from './header.js';
|
||||
@@ -353,6 +353,7 @@ export async function sendMessage() {
|
||||
}
|
||||
|
||||
msgsToAdd.forEach(m => currentSession.messages.push(m));
|
||||
enableAutoScroll();
|
||||
renderMessages();
|
||||
|
||||
// 立即保存
|
||||
|
||||
Reference in New Issue
Block a user