AI 对话页流式输出滚动跟随:420ms 惯性窗口是怎么来的
做 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。它们都不是理论值,是在真机上一点点试出来的——做这类交互,与其在纸上推导,不如上真机多滑几遍,手感会告诉你答案。
- 点赞
- 收藏
- 关注作者
评论(0)