fix(chat): 运行中逐帧贴底锁,修复块外框与内容移动不同步
This commit is contained in:
parent
a7a40a8a2f
commit
677604b538
@ -1215,6 +1215,52 @@ const { anchorBlockElement, stopAll: stopBlockExpansionAnchors } = useBlockExpan
|
|||||||
scrollRef,
|
scrollRef,
|
||||||
{ stopScroll }
|
{ stopScroll }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// —— 运行中逐帧贴底锁 ——
|
||||||
|
// 背景:stick 库的 resize 追底在 ResizeObserver 回调里再经 rAF 才真正写 scrollTop,
|
||||||
|
// 且它观察的 .messages-flow 自身高度要等 virtua 回写占位高度后才变化,整体滞后内容
|
||||||
|
// 增长 1~2 帧。运行中块高度过渡(思考块自动展开/流式增长/手动展开)的每一帧会先按
|
||||||
|
// 新高度绘制、下一帧才被追底拉回,表现为「块外框先动、内部文字慢一步」的不同步。
|
||||||
|
// 这里在对话运行期间持有 rAF(绘制前强制读取 scrollHeight 并同步吸附底部),让高度
|
||||||
|
// 增长与滚动补偿落在同一帧。用户上滚脱离锁定(escapedFromLock/isAtBottom 变 false)
|
||||||
|
// 时自动停止吸附,滚回底部后自动恢复;非运行期仍由 useBlockExpansionAnchor 处理。
|
||||||
|
let liveStickLockRaf: number | null = null;
|
||||||
|
|
||||||
|
function liveStickLockTick() {
|
||||||
|
liveStickLockRaf = null;
|
||||||
|
if (!props.streamingMessage) return;
|
||||||
|
const el = scrollRef.value;
|
||||||
|
if (el && isAtBottom.value && !escapedFromLock.value) {
|
||||||
|
const maxTop = el.scrollHeight - el.clientHeight;
|
||||||
|
if (maxTop - el.scrollTop > 1) {
|
||||||
|
markProgrammaticHint('ChatArea.liveStickLock');
|
||||||
|
el.scrollTop = maxTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liveStickLockRaf = requestAnimationFrame(liveStickLockTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopLiveStickLock() {
|
||||||
|
if (liveStickLockRaf !== null) {
|
||||||
|
cancelAnimationFrame(liveStickLockRaf);
|
||||||
|
liveStickLockRaf = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.streamingMessage,
|
||||||
|
(streaming) => {
|
||||||
|
if (streaming) {
|
||||||
|
if (liveStickLockRaf === null) {
|
||||||
|
liveStickLockRaf = requestAnimationFrame(liveStickLockTick);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopLiveStickLock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
const thinkingRefs = new Map<string, HTMLElement | null>();
|
const thinkingRefs = new Map<string, HTMLElement | null>();
|
||||||
let scrollbarResizeObserver: ResizeObserver | null = null;
|
let scrollbarResizeObserver: ResizeObserver | null = null;
|
||||||
let bounceTraceCount = 0;
|
let bounceTraceCount = 0;
|
||||||
@ -2082,6 +2128,7 @@ watch(scrollRef, () => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
detachBounceListener();
|
detachBounceListener();
|
||||||
stopBlockExpansionAnchors();
|
stopBlockExpansionAnchors();
|
||||||
|
stopLiveStickLock();
|
||||||
if (scrollbarResizeObserver) {
|
if (scrollbarResizeObserver) {
|
||||||
scrollbarResizeObserver.disconnect();
|
scrollbarResizeObserver.disconnect();
|
||||||
scrollbarResizeObserver = null;
|
scrollbarResizeObserver = null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user