告别卡顿!用SSE和Vue3打造丝滑的AI对话打字机效果(附完整代码)

张开发
2026/4/12 16:55:50 15 分钟阅读

分享文章

告别卡顿!用SSE和Vue3打造丝滑的AI对话打字机效果(附完整代码)
用SSE与Vue3实现AI对话的流畅打字机效果在开发AI对话应用时流畅的交互体验至关重要。想象一下当用户提问后答案像真人打字一样逐字呈现这种即时反馈不仅能提升参与感还能让复杂信息的吸收变得更加自然。本文将带你用Vue3和SSE技术实现这种专业级的交互效果。1. 为什么选择SSE而非WebSocket在实时通信方案选型时开发者常面临SSE与WebSocket的抉择。让我们先看看两者的核心差异特性SSEWebSocket通信方向服务端到客户端的单向通信全双工通信协议基础基于HTTP独立的ws协议断线重连内置支持需手动实现数据传输格式文本格式二进制/文本浏览器兼容性除IE外的现代浏览器广泛支持对于AI对话这种服务端推送场景SSE具有天然优势// 典型SSE连接示例 const eventSource new EventSource(/api/chat-stream); eventSource.onmessage (event) { console.log(收到数据:, event.data); };提示当只需要服务端推送时SSE比WebSocket节省约40%的资源开销2. Vue3的状态管理方案在组合式API中我们需要精心设计响应式系统来处理流式数据。以下是核心状态结构import { ref, reactive } from vue; interface MessageBlock { id: string; content: string; isComplete: boolean; } const chatState reactive({ // 已渲染完成的完整消息块 blocks: [] as MessageBlock[], // 当前正在输入的消息 currentBlock: { rawText: , displayText: }, // 打字机队列 typingQueue: [] as string[], // 打字机状态 isTyping: false });关键设计要点分离原始数据与渲染数据避免频繁操作DOM队列缓冲机制平滑处理网络波动块状存储结构支持消息分段渲染3. 实现打字机动画的核心逻辑打字机效果的秘密在于队列处理和定时渲染的配合function typeNextCharacter() { if (chatState.typingQueue.length 0) { chatState.isTyping false; return; } const char chatState.typingQueue.shift(); chatState.currentBlock.rawText char; chatState.currentBlock.displayText char; // 检测句子结束 if (isSentenceEnd(char, chatState.currentBlock.rawText)) { finalizeCurrentBlock(); } // 控制打字速度 (30-70ms/字符模拟真人输入) const speed 50 Math.random() * 40; setTimeout(typeNextCharacter, speed); } function isSentenceEnd(char: string, text: string): boolean { // 中文句号、问号等作为句子结束标志 return /[。\n]/.test(char) || text.endsWith(\n\n); } function finalizeCurrentBlock() { chatState.blocks.push({ id: Date.now().toString(), content: chatState.currentBlock.rawText, isComplete: true }); chatState.currentBlock { rawText: , displayText: }; }动画优化技巧随机速度在基础延迟上增加随机值避免机械感光标闪烁用CSS动画增强真实感.typing-cursor::after { content: |; animation: blink 1s step-end infinite; color: #4fa3f7; } keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }4. 性能优化关键策略当处理长文本时不当的实现会导致明显卡顿。以下是经过实战验证的优化方案内存管理三原则定期清理已完成的消息块限制历史消息保留数量使用虚拟滚动处理长对话// 使用Web Worker处理解析工作 const parserWorker new Worker(./markdown-parser.js); parserWorker.onmessage (e) { const { raw, html } e.data; enqueueText(html); // 将结果加入打字队列 }; function enqueueText(text: string) { // 分批处理长文本 (每50字符为一组) const chunkSize 50; for (let i 0; i text.length; i chunkSize) { const chunk text.slice(i, i chunkSize); chatState.typingQueue.push(...chunk.split()); } if (!chatState.isTyping) { chatState.isTyping true; typeNextCharacter(); } }渲染性能对比测试方法1万字耗时内存占用流畅度直接innerHTML120ms高卡顿文档片段分批渲染250ms中较流畅虚拟DOM打字机效果400ms低极流畅5. 完整实现示例以下是集成SSE和打字机效果的Vue3组件实现script setup langts import { onMounted, onUnmounted, reactive } from vue; // 状态初始化 const state reactive({ messages: [] as Array{ id: string content: string isComplete: boolean }, currentContent: , queue: [] as string[], isTyping: false, eventSource: null as EventSource | null }); // 建立SSE连接 function connectSSE() { state.eventSource new EventSource(/api/ai-stream); state.eventSource.onmessage (event) { const data JSON.parse(event.data); processIncomingData(data.text); }; state.eventSource.onerror () { console.error(SSE连接错误); reconnect(); }; } // 处理接收到的数据 function processIncomingData(text: string) { // 将新字符加入队列 state.queue.push(...text.split()); // 如果当前没有在输入启动打字机 if (!state.isTyping) { typeNextCharacter(); } } // 打字机核心逻辑 function typeNextCharacter() { if (state.queue.length 0) { state.isTyping false; return; } state.isTyping true; const char state.queue.shift()!; state.currentContent char; // 检测段落结束 if (/[\n。]/.test(char)) { finalizeCurrentMessage(); } // 随机打字速度 (40-80ms/字符) setTimeout(typeNextCharacter, 40 Math.random() * 40); } function finalizeCurrentMessage() { state.messages.push({ id: Date.now().toString(), content: state.currentContent, isComplete: true }); state.currentContent ; } // 断线重连逻辑 function reconnect() { setTimeout(() { if (state.eventSource?.readyState EventSource.CLOSED) { connectSSE(); } }, 3000); } // 生命周期钩子 onMounted(connectSSE); onUnmounted(() { state.eventSource?.close(); }); /script template div classchat-container div v-formsg in messages :keymsg.id classmessage-block {{ msg.content }} /div div v-ifcurrentContent classtyping-block {{ currentContent }}span classtyping-cursor/span /div /div /template style scoped .typing-cursor::after { content: |; animation: blink 1s step-end infinite; } keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } /style在真实项目中这种实现方式能够处理每分钟上千字的流式输出同时保持60fps的流畅动画效果。一个常见的优化陷阱是过度使用Vue的响应式系统——对于高频更新的打字动画直接操作DOM有时比依赖响应式数据更高效。

更多文章