AI 对话页流式输出滚动跟随:420ms 惯性窗口是怎么来的

举报
yd_296967770 发表于 2026/11/06 15:35:21 2026/11/06
【摘要】 記錄微信小程序 AI 對話頁流式輸出時的滾動跟隨方案:為何只看 scrollTop 會誤判,如何改用觸摸狀態追蹤使用者意圖,並用 420ms 慣性窗口、200ms 程式滾動鎖定和 80px 貼底閾值解決抖動與誤判。

做 AI 提示词助手小程序时,对话页有个看起来很小的需求:AI 回复流式输出时,视图要自动跟到最下面,让用户一直看到最新吐出来的字。但用户一旦手动往上滑去看前面的内容,就不能再把他拽回底部。这个需求我前后改了三轮,最后的方案是直接跟踪触摸状态,再加一个 420ms 的惯性窗口。这篇文章记录一下为什么是这个方案,以及中间踩过的坑。

第一版为什么不行:只看 scrollTop 会误判

最直觉的做法是监听 scroll-view 的滚动事件:只要 scrollTop 不在底部,就认为用户离开了,停止跟随;回到贴底位置,再恢复跟随。写出来很简单,实测却有两个毛病。

第一个毛病是流式输出本身会“撑高”内容。新行不断加进来,scrollHeight 变大,scrollTop 不变,视图相对底部的位置就变了。如果只看 scrollTop 和底部的距离,会以为用户离开了底部,其实他手指根本没动,只是内容长高了。结果就是跟随一会儿断一会儿续,视图一跳一跳的。

第二个毛病更隐蔽:手指松开后,滚动还会因为惯性继续滑一小段。这段时间里的 scrollTop 变化不是用户的主动操作,但如果这时程序正好执行了一次跟随滚动,两者就会打架——程序刚把视图拉到底,惯性又把它带上去一点,接着程序又拉回来,屏幕来回抖。

所以只看位置不够,还得知道“这次滚动是谁引起的”。

改成跟踪触摸状态:手指才是意图

第二版改成直接跟踪触摸状态,用三个变量记录:

  • _touching:手指是否正按在消息列表上;
  • _userDrag:用户是否正在拖动;
  • _touchMoved:这次触摸有没有真正移动过。

逻辑很简单:只有手指把内容往上带,才取消跟随。具体判断是,在 onMsgScroll 里比较这次和上次的 scrollTop——只有 scrollTop 比上一次小了 6px 以上,才算用户往上滑了。内容变高时 scrollTop 不变,自然不会误判。

手指松开时(onMsgTouchEnd / onMsgDragEnd)把 _userDrag 清掉,同时记一个 _inertiaUntil = Date.now() + 420,也就是惯性窗口的截止时间。松手后的 420ms 内,程序滚动仍然被挡住,等惯性滑完了再恢复跟随。420 这个数不是算出来的,是实测调出来的:太短了盖不住惯性,太长了用户松手后会觉得跟随“反应迟钝”,420 是手感上刚好的那个点。

跟随函数本身也有守卫。followBottom(force) 里先检查一串条件:没贴底、正在拖动、手指按着、在惯性窗口内,任何一条满足就直接返回。只有用户点了“回到底部”按钮或者刚发出消息时,才用 force=true 强行跟随,绕过这些守卫。

几个边界情况,都是实测撞出来的

第一,程序滚动之后要“锁定”一下。 程序调用 node.scrollTo 把视图拉到底部后,滚动事件会立刻回一个 scrollTop 变化。如果这时去判断“用户是不是离开了”,很容易把自己这次滚动当成用户操作。所以每次程序滚动后记一个 _scrollLockUntil = Date.now() + 200,200ms 内收到的滚动事件只用来确认“已经在底部了”,不做取消跟随的判断。

第二,scrollTop 莫名其妙掉到 0。 有一次实测发现,视图明明贴着底,scrollTop 突然从几百掉到 0 附近,手指完全没动。后来确认这是渲染抖动:新行还没排进布局,测量到的内容高度偏小。我的处理是:没有手指、没有拖动、scrollTop 小于 8 且上一次大于 80,这种情况直接判定为渲染抖动,把视图拉回去,而不是取消跟随。

第三,测量偏小会把视口弹上去。 跟随滚动时先算 max = 内容高度 - 视口高度,如果新行还没排进去,算出来的 max 会比当前 scrollTop 还小,直接设过去会把视图往上弹。所以先比较一下:max + 2 < 当前 scrollTop 时不执行,等 40ms 后重试,最多重试 4 次。配合一个 _scrollGen 代号,每次用户操作都让代号加一,旧的重试发现代号对不上就直接丢弃,不会跟新的操作打架。

第四,贴底阈值。 判断“在底部”不是要求 scrollTop 精确等于最大值,而是内容底部和视口底部的距离小于 80px 就算贴底。留这个余量是因为流式输出时内容一直在变,精确相等几乎不存在,80px 内都算“用户还在看最新内容”。

小结

回头看,这个功能的本质不是“滚动”,而是“判断意图”。scrollTop 只能告诉你视图在哪,不能告诉你它为什么在那。改成跟踪触摸状态之后,判断依据就从“位置”变成了“动作”:手指往上带才取消跟随,内容自己长高不取消,惯性滑行不取消,渲染抖动不取消。

三个数字可以当作参考:惯性窗口 420ms、程序滚动锁定 200ms、贴底阈值 80px。它们都不是理论值,是在真机上一点点试出来的——做这类交互,与其在纸上推导,不如上真机多滑几遍,手感会告诉你答案。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。