JavaScript中SSE怎么在前端配合Markdown渲染器实现类似AI对话的打字机特效

胖瑶酱_9675

胖瑶酱_9675

2026-07-25

855人浏览

原创

sse流式数据配合markdown渲染实现ai对话打字机效果,关键在于按语义块(句子/段落/token)累积、实时更新与安全渲染,需用fetch-event-source替代eventsource、增量解析markdown、控制渲染节奏并智能滚动。

javascript中sse怎么在前端配合markdown渲染器实现类似ai对话的打字机特效

要让SSE流式数据在前端配合Markdown渲染器呈现AI对话的“打字机效果”,关键不是单纯逐字渲染,而是按语义块(如句子、段落或token)拼接 → 实时更新 → 渲染为带格式的Markdown内容。整个流程需兼顾流式解析、文本累积、安全渲染与视觉反馈。


一、SSE连接必须支持POST和自定义请求头

浏览器原生 EventSource 只支持 GET,且无法携带 Authorization、Content-Type 等关键请求头,不适合真实AI接口(OpenAI/Ollama等均要求 POST + token)。
✅ 正确做法:用 @microsoft/fetch-event-source 或 uni.request({ enableChunked: true })(小程序端)

import { fetchEventSource } from '@microsoft/fetch-event-source';

let currentContent = '';
const streamingRef = useRef(null);

fetchEventSource('/api/chat', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` },
  body: JSON.stringify({ message: userInput }),
  onmessage(event) {
    try {
      const data = JSON.parse(event.data);
      // OpenAI格式:data.choices[0].delta.content
      const delta = data?.choices?.[0]?.delta?.content || '';
      currentContent += delta;
      streamingRef.current = currentContent;
      // 触发重新渲染(React中用setState)
      setAiMessage(currentContent);
    } catch (e) {
      console.warn('SSE数据解析失败,跳过:', event.data);
    }
  },
});

⚠️ 注意:后端必须返回 Content-Type: text/event-stream,每条消息以 data: {...}\n\n 结尾,UTF-8编码。


二、Markdown渲染器需支持增量更新与实时高亮

不能每次全量重解析整段文本(性能差、闪烁),应:

  • 使用轻量级、可增量调用的 Markdown 解析器(如 marked 或 markdown-it)
  • 避免直接 innerHTML = md.render(text) —— 会清空已有DOM并重建,打断打字动画
  • 推荐方案:用 <streamingmarkdown></streamingmarkdown> 组件封装,内部做「差异更新」或「光标锚定」

示例(基于 markdown-it + 自定义光标插件):

Markdown → 图片卡片
Markdown → 图片卡片

当用户想要将Markdown内容转换为可分享的图片卡片时使用。触发词包括:转图片、做卡片、生成海报、生成分享图、把这个转成图片、export as image

下载
// 插件:在末尾添加闪烁光标
function cursorPlugin(md) {
  md.core.ruler.after('inline', 'cursor', (state) => {
    if (state.src.endsWith('\n') || !state.src.trim()) return;
    const token = state.push('cursor_open', 'span', 0);
    token.markup = 'cursor';
  });
}

const md = markdownit({ html: true }).use(cursorPlugin);

渲染时只处理新增部分(或全量但启用 skipHtml: false + xhtmlOut: true 保证安全):

<streamingmarkdown content="{aiMessage}"></streamingmarkdown>
// 内部:md.render(aiMessage) → 返回含 <code>、<p>、<em> 的HTML 字符串
// 再用 dangerouslySetInnerHTML 渲染(确保内容可信)或用 react-markdown 安全渲染</em></p></code>

三、打字机效果 ≠ 逐字,而是“语义流+视觉节奏”

真实AI流式输出是按 token 或小段落推送(如 "Hello" → " world" → "!"),直接逐字渲染会卡顿、断句奇怪。更合理的方式:

  • ✅ 按自然停顿(逗号、句号、换行)做「软暂停」
  • ✅ 用 setTimeout 或 requestAnimationFrame 控制最小渲染间隔(如 ≥30ms/次),避免过快闪动
  • ✅ 渲染前对 currentContent 做简单清理:去掉未闭合的 <code>、截断不完整 Markdown 行
// 简单防抖 + 断句优化
const safeRender = (raw) => {
  // 移除孤立反引号、未闭合的链接等(避免渲染错误)
  let cleaned = raw.replace(/`[^`]*$/g, '`'); // 补全单个反引号
  cleaned = cleaned.replace(/\*\*[^*]*$/g, '**'); // 同理粗体
  return cleaned;
};

setAiMessage(safeRender(currentContent));

四、滚动与交互体验细节不能忽略

用户正在看历史消息时,自动滚动到底部会打断阅读:

  • 监听聊天容器 scrollTop 与 scrollHeight 差值
  • 仅当容器已滚动到底部(scrollTop + clientHeight >= scrollHeight - 2)才触发 scrollIntoView()
  • 提供「新消息提示」按钮(如右下角小红点),点击再滚动
useEffect(() => {
  if (isAtBottom && aiMessage) {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }
}, [aiMessage]);

SSE + Markdown 打字机效果本质是「流式文本管道」:接收 → 累积 → 清洗 → 解析 → 渲染 → 滚动。每环都影响最终观感,但核心逻辑清晰——别把SSE当成逐字信号源,而应视作语义片段流;别把Markdown当成静态转换器,而应看作可响应式更新的富文本引擎。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关专题

更多
python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2163

5

前端如何实现即时通讯
前端如何实现即时通讯

实现即时通讯的方法有WebSocket、Long Polling、Server-Sent Events、WebRTC等等。详细介绍:1、WebSocket,它可以在客户端和服务器之间建立持久连接,实现实时的双向通信,前端可以使用 WebSocket API来创建WebSocket连接,并通过发送和接收消息来实现即时通讯;2、Long Polling,是一种模拟实时通信的技术等等。

2023.10.09

4623

6

前端和后端的区别
前端和后端的区别

前端关注的是用户界面的设计和交互,而后端则注重数据处理和逻辑控制。想了解更多前端后端的相关内容,可以阅读本专题下面的文章。

2024.03.19

5770

13

php和前端的关联介绍
php和前端的关联介绍

php既可以作为前端语言,也可以作为后端语言。想了解更多php和前端的相关内容,可以阅读本专题下面的文章。

2024.03.22

5198

10

前端外包工作内容有哪些
前端外包工作内容有哪些

前端外包工作内容包括:1. 网站和应用程序开发;2. 用户界面和交互设计;3. 用户体验优化;4. 设计和视觉开发;5. 跨浏览器兼容性;6. 性能优化;7. 维护和更新;8. 项目管理和沟通。想了解更多前端的相关内容,可以阅读本专题下面的文章。

2024.05.22

743

5

js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

4306

5

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

2023.07.04

1089

3

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

2023.07.04

4224

6

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

2023.09.01

860

4

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebStorm 官方调试文档
WebStorm 官方调试文档

共0课时 | 0人学习

React 教程
React 教程

共58课时 | 12万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 6.5万人学习