AI流式渲染打字机效果抖动?节流方案踩坑实录

上线那天我盯着对话框看了半小时,越看越烦------AI 一吐字,整段文字就抖一下,像在打摆子。产品同学截图甩群里:「这是抖屏 bug 吗?」不是 bug,是我自己写的打字机效果,把性能问题写进去了。

记一下我怎么从「一秒重排 60 次」调到「肉眼丝滑」的,中间踩的坑都挺典型。

先搞清楚抖动从哪来

SSE 推过来的 token 是一小块一小块的,有时一次来俩字,有时半个字(UTF-8 多字节被切了)。我最早的写法很朴素:

ini 复制代码
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let full = '';
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  full += decoder.decode(value, { stream: true }); // stream:true 很关键
  setText(full); // 罪魁祸首
}

decoder.decode 不加 stream: true 那次,我吃了个大亏------中文经常显示成「�」,因为一个汉字的三个字节被拆到两次 chunk 里了。加上 stream: true,解码器会缓住不完整的字节,等下一块拼齐再吐。这个坑排查了我一下午。

抖动的真凶是 setText(full)。后端快的时候一秒能 push 三四十次,每次 setState 都触发 React 重渲染整段 Markdown,长回答里有代码块、列表、表格,重排成本极高。FPS 直接掉到 20 出头。

节流,但别用现成的 throttle

第一反应是 lodash throttle,但它有个问题:尾部那次更新可能被丢掉,导致最后几个字显示不全。我改成用 requestAnimationFrame 攒一帧渲染一次:

ini 复制代码
let pending = '';
let rafId: number | null = null;

function pushChunk(chunk: string) {
  pending += chunk;
  if (rafId != null) return;
  rafId = requestAnimationFrame(() => {
    setText(prev => prev + pending);
    pending = '';
    rafId = null;
  });
}

一帧最多渲染一次,天然贴着屏幕刷新率走,60Hz 屏就是 16.6ms 一次,重排次数从一秒 40 次压到 60 次封顶但每次增量极小。结束时记得把残留的 pending flush 一次,别漏字。

打字机的「匀速」是装出来的

光攒帧还不够顺,因为后端吐字本身忽快忽慢------卡 800ms 不动,然后哗一下来 50 个字。用户体感是「一顿一顿」。我加了个本地队列做缓冲,让字符以恒定速度出来:

ini 复制代码
const queue: string[] = [];
function enqueue(s: string) { queue.push(...s); }

function tick() {
  // 队列堆积多就一帧多吐几个,别让缓冲无限膨胀
  const step = Math.max(1, Math.floor(queue.length / 8));
  const out = queue.splice(0, step).join('');
  if (out) setText(prev => prev + out);
  requestAnimationFrame(tick);
}

step 那行是我调了好几轮的------固定一帧吐一个字,遇到长回答能拖到几十秒还没吐完,用户早走了;固定吐十个又太快没了打字感。按积压量动态调速,积压越多吐越快,体验最自然。

一个没解决干净的取舍

Markdown 边流边渲染,代码块在没闭合前 ``````````` 是残缺的,highlight.js 会闪一下未高亮的纯文本再变色。我试过等代码块闭合再渲染,但那样代码块会整块「啪」地出现,破坏打字感。最后选了「先纯文本流出、闭合后再上色」,接受那一下轻微闪烁------两害相权,闪一下比卡顿强。这块到现在我也没找到完美解,有更优雅的思路欢迎评论区拍我。

模型那头我没自己折腾,对话和工具编排直接挂在讯飞 这类 MaaS 上,现成的模型 API 调就完事,我把精力全砸在前端这点丝滑上。

你们做流式渲染抖动是怎么治的?评论区交个底。

相关推荐
zzzzzz31036 分钟前
anthropics/skills:高关注度“官方技能”项目,应该怎样读
人工智能·开源·github
moonsims40 分钟前
Voliro 无人机-Aerial Mobile Robot(空中移动机器人):把无人机从“飞过去拍摄”,升级成“飞过去并与目标物理接触、测量甚至操作”
前端·人工智能·安全·无人机·量子计算
职场的momo1 小时前
用户讨论:算法开发Offer决赛:大疆纯后端Agent与网易游戏测试,对内AI跳槽难?
人工智能·游戏·跳槽
wish3662 小时前
EmployeeAssistant 智能问答服务:基于 pgvector 与 LLM 的企业知识库助手
人工智能·语言模型·自然语言处理·local llm
检信智能3 小时前
检信 Allemotion 多模态融合技术,解析非接触情绪采集的底层逻辑
人工智能·语音识别
RoboWizard3 小时前
固态硬盘对游戏帧数有影响吗?
人工智能
2301_768103496 小时前
AI视频创作Agent实战03:DeepSeek文案裂变与草稿版本控制
人工智能
火山引擎开发者社区6 小时前
Anker 首届黑客松挑战赛|9 月 7 日报名启动
人工智能
Dawson Zhu8 小时前
工作流与 Agent 的工程选型:从“控制权归属“看 LLM 应用架构
人工智能·语言模型·架构·aigc·agi
计算机源码社8 小时前
【大数据项目实战】基于大数据的影视内容生态综合质量分析与可视化-基于数据挖掘的影视内容类型共现与口碑聚类分析系统
大数据·人工智能·python·数据挖掘·数据分析·毕业设计·课程设计