AI聊天自动跟随滚动,附回到底部按钮

AI 一边流式打字,聊天框一边自动滚到底------听着简单,做起来全是边界。最坑的是:用户明明往上翻看历史,你的自动滚动还在把他强行拽到底,气得人想砸键盘。这篇讲怎么做「智能跟随」,React,纯逻辑,框架无关也能套。

一句话原则

只有当用户已经贴着底部时,才跟随新内容滚动;用户一旦手动往上翻,立刻停止跟随,转而显示「回到底部」按钮。

所以核心是一个状态:用户现在是不是「在底部附近」。

判断在不在底部

不能用 ===,得给一个容差,因为流式追加内容时 scrollHeight 一直在变,精确相等几乎不可能成立。

ini 复制代码
function isNearBottom(el: HTMLElement, threshold = 80) {
  return el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
}

80px 这个阈值是我试出来的。太小了,用户内容刚追加一行就判定「离开底部」,按钮乱闪;太大了,明明翻上去半屏还在硬跟随。

监听滚动,记住用户意图

ini 复制代码
const [stick, setStick] = useState(true);   // 是否跟随
const boxRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  const el = boxRef.current!;
  const onScroll = () => setStick(isNearBottom(el));
  el.addEventListener('scroll', onScroll, { passive: true });
  return () => el.removeEventListener('scroll', onScroll);
}, []);

这里有个隐蔽的坑:程序触发的 scrollTo 也会触发 scroll 事件 ,所以自动滚动本身会回调 onScroll。好在只要我们滚到的就是底部,isNearBottom 返回 true,stick 还是 true,逻辑自洽,不用额外加锁。

新内容来了再决定滚不滚

把消息列表变化作为依赖,每次 delta 进来检查 stick:

ini 复制代码
useEffect(() => {
  if (!stick) return;
  const el = boxRef.current!;
  el.scrollTop = el.scrollHeight;
}, [messages, stick]);

注意用 scrollTop = scrollHeight 这种瞬时跳,不要在流式追加时用 behavior: 'smooth' 。平滑滚动每帧都没追上下一帧的新内容,会出现永远在缓慢滚、永远到不了底的「拉橡皮筋」效果,看着特别廉价。smooth 只留给点「回到底部」按钮那一下。

回到底部按钮

stick 为 false 时才显示。点一下平滑滚到底,并把 stick 重新置 true:

ini 复制代码
{!stick && (
  <button className="to-bottom" onClick={() => {
    const el = boxRef.current!;
    el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });
    setStick(true);
  }}>
    ↓ 回到底部
  </button>
)}

进阶一点,可以在按钮上显示「离开底部期间来了几条新消息」。记一个计数,stick 变 false 后每来一条 +1,点按钮清零:

ini 复制代码
const unreadRef = useRef(0);
// 在追加消息的地方:
if (!stick) unreadRef.current++;

两个真实缺陷

第一,图片/代码块异步撑高。一条消息里有张图,图加载完高度突变,此时如果用户刚好在底部,会被往下顶。我的处理是给图片 onLoad 也触发一次 if (stick) scrollToBottom(),但这治标不治本,懒得追求完美就接受偶尔的小跳。

第二,移动端惯性滚动。iOS 的橡皮筋回弹会让 scrollTop 短暂为负或超界,isNearBottom 偶尔误判。加个 Math.max(0, ...) 兜底能缓解,没法根治。

整体这套逻辑就一个状态变量撑着,不用引任何库。把「跟不跟随」交给用户的滚动意图,而不是无脑滚到底,体验差距非常大。

最后扯一句正题。这种纯前端交互打磨我能折腾很久,但底下的对话生成我没自己搭------直接调讯飞(agent.xfyun.cn/home?ch=Age... Agent发布的流式接口,它是 MaaS,模型和算力在它那边,我前端专心做滚动跟随这种体验活,分工挺清爽。

相关推荐
lzhdim41 分钟前
AI的分类
人工智能·算法·机器学习·分类·数据挖掘
程序员海军2 小时前
用了两年多 GPT,我最近开始换模型了,还折腾了个求职避坑 Agent
前端·人工智能·后端
AIsoft_86893 小时前
人才评估中的关键信息如何沉淀?
人工智能·iphone
紧跟先前的步伐3 小时前
Go AI Agent 实战 06|JSON 结构化输出与 Go 后端校验
人工智能·ai·golang
vibecoding774 小时前
Codex 分层用模型完整教程:让 GPT-6.1 Sol 拍板、GPT-6 Luna 跑侦察
人工智能·gpt
新知图书4 小时前
1.3 启动第一个机器人
人工智能·机器人
明志数科5 小时前
自主探索范式下机器人示教数据的工程定位:少量示范为何对质量更敏感
人工智能·机器学习·机器人
Maynor9965 小时前
WorkBuddy 一键接通 gpt-6.1-sol:MomoAI / TryAllAPI 把最新模型接到桌面
人工智能·gpt·ai·大模型·编程工具
火山引擎开发者社区5 小时前
问得出、搜得准、信得过:ContextSearch 重构销售检索体验
人工智能
Raymond16885 小时前
AI配音工具长文本合成架构技术解析:全局音色锚定与声学状态跟踪
人工智能·架构·实时互动·视觉检测·音视频·语音识别