AI 场景
AI 对话是虚拟滚动最难的一类场景:内容持续变化、高度完全不可预知、 用户和程序同时在滚动。先看效果——
微应用尚未挂载。
点一个快捷提示或自己输入一句发送,试着在生成过程中向上滚、再滚回底部。 生成过程里会依次插入思考、工具调用、代码块——每一次插入都是一次高度跳变。 每个动作的预期行为见 流式输出与贴底跟随。
这一节不是新的 API,而是把已有能力组合起来解决这类场景的方法。
为什么普通虚拟列表在这里会失稳
| 场景特征 | 常见实现为什么出问题 |
|---|---|
| 流式输出:一条消息的高度每几十毫秒变一次 | 若按「测出新尺寸 → 按差值补偿滚动偏移」处理,误差会逐次累积,而补偿本身又触发新的滚动事件,很容易抖动甚至回弹 |
| 边生成边滚动,用户随时可能上滑去读历史 | 多数实现分不清 scrollTop 是用户改的还是程序改的,只能靠「上次程序化写入的值」去猜,与异步滚动事件构成竞态 |
| 消息里有代码块、表格、公式,渲染成本高 | 项反复进出视口就反复重渲染,长回答滚动时掉帧 |
| 几万条历史,需要往上翻 | 头部插入数据后视口内容被推走,需要手动补偿位移 |
这套实现的应对
两个设计选择决定了上面四种情形不会失稳。
一、把「视觉上该维持什么」表达成一个可求解的目标,而不是一串增量补偿。 尺寸每次变化都重新求解这个目标,而不是累加位移。因为求解的是不变量, 重复执行是幂等的——不需要收敛判据,也不需要重试上限。 流式输出下的稳定跟随、头部插入后的位移补偿、折叠长消息后的位置维持, 在这个模型里是同一件事的不同分支,而不是三套互相打架的补偿逻辑。
二、滚动来源是确定的,不是猜的。 偏移量由 JS 掌管(容器 overflow: hidden),用户输入与程序化定位走两个不同的入口。 于是「这次滚动是谁发起的」在代码里是已知量,"用户一上滑就解除跟随"这类判断 不依赖任何时间窗口,也就不存在与异步滚动事件的竞态。
具体做法
- 流式输出与贴底跟随 —— 跟随的建立与解除,逐 token 更新的正确写法
- 整段复制 —— 跨越未渲染项的选区复制,补上虚拟滚动的一处正确性缺口
- 搜索与引用跳转 —— 在数据里搜索、跳到任意一条并高亮
- 会话历史 —— 万级历史的向上加载、未读角标、会话切换
- Agent 执行流 —— 高频追加的日志视图、可折叠的工具调用
- 语义滚动条 —— 把长列表的结构画出来,点一下跳到那一段
相关基础
这两页假设你已经了解 分页与无限加载 里的 loadMore 模型。 若想了解跟随背后的滚动机制,可以读 滚动是怎么工作的。