fix(chat): 修复内容大幅增长时滚动锁定不及时的问题

- 调整 vue-stick-to-bottom resize spring 参数,加快动画响应
  (damping 0.68→0.5, stiffness 0.11→0.28, mass 1.05→0.75)
- 在 scrollListener 中添加高度突变检测,当 scrollHeight 大幅增长
  (超过 200px 或 viewport 35%)且用户未脱离锁定时,用 instant
  瞬间追底,绕过 spring 动画延迟和去重机制

修复场景:思考块展开、Markdown 表格渲染等造成大幅度垂直距离增加时
滚动锁定失效或锁住不及时的问题
This commit is contained in:
JOJO 2026-06-09 10:47:22 +08:00
parent 2fcd241d94
commit 8d87068ba7

View File

@ -790,9 +790,9 @@ const {
stopScroll
} = useStickToBottom({
resize: {
damping: 0.68,
stiffness: 0.11,
mass: 1.05
damping: 0.5,
stiffness: 0.28,
mass: 0.75
},
initial: 'instant'
});
@ -809,6 +809,7 @@ let lastUserWheelUpTs = 0;
let lastProgrammaticHintTs = 0;
let lastProgrammaticHintSource = '';
let suppressUserIntentUntil = 0;
let isHandlingLargeGrowth = false;
let scrollListener: ((event: Event) => void) | null = null;
let wheelListener: ((event: WheelEvent) => void) | null = null;
let traceAttachLogged = false;
@ -924,6 +925,32 @@ function attachBounceListener() {
const heightDelta = height - lastObservedHeight;
lastObservedTop = top;
lastObservedHeight = height;
// scrollHeight /
// escapedFromLock=false
// instant spring
// Markdown
const largeGrowthThreshold = Math.max(200, target.clientHeight * 0.35 || 200);
if (
!isHandlingLargeGrowth &&
heightDelta > largeGrowthThreshold &&
!escapedFromLock.value
) {
isHandlingLargeGrowth = true;
markProgrammaticHint('ChatArea.largeGrowth');
suppressUserIntentUntil = Date.now() + 900;
scrollToBottom({ animation: 'instant', preserveScrollPosition: false });
bounceTraceLog(
'large-growth:instant-scroll',
{ heightDelta, threshold: largeGrowthThreshold, ...getScrollMetrics(target) },
'large-growth:instant-scroll',
120
);
setTimeout(() => {
isHandlingLargeGrowth = false;
}, 400);
}
const now = Date.now();
const trusted = (event as any).isTrusted === true;
const closeToProgrammaticHint = now - lastProgrammaticHintTs <= 140;