你想要实现的是通过 SSE(Server-Sent Events)接收后端返回的 AI 数据,并在前端以打字机的逐字输出效果展示出来,这个需求在 AI 对话、实时文本生成场景中非常常见。
实现思路
- 建立 SSE 连接,监听后端推送的文本数据
- 接收数据后,通过定时器逐字将文本渲染到页面
- 处理连接关闭、错误等异常情况,保证体验的完整性
- 提供暂停 / 继续、清空等辅助功能(增强实用性)
完整代码实现
以下是可直接运行的前端代码(原生 JavaScript 实现,无框架依赖):
html
预览
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSE 打字机效果展示</title>
<style>
.container {
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
#output {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
min-height: 200px;
font-size: 16px;
line-height: 1.6;
margin: 20px 0;
white-space: pre-wrap; /* 保留换行和空格 */
}
.controls {
margin-bottom: 20px;
}
button {
padding: 8px 16px;
margin-right: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
background-color: #007bff;
color: white;
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
button#pause {
background-color: #ffc107;
color: #333;
}
button#clear {
background-color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<div class="controls">
<button id="connect">开始连接 SSE</button>
<button id="pause" disabled>暂停输出</button>
<button id="clear">清空内容</button>
</div>
<div id="output"></div>
</div>
<script>
// 核心变量
let sse = null; // SSE 连接实例
let textBuffer = ''; // 存储后端推送的所有文本
let currentIndex = 0; // 当前显示到的字符索引
let typingTimer = null; // 打字机定时器
let isPaused = false; // 是否暂停输出
// DOM 元素
const connectBtn = document.getElementById('connect');
const pauseBtn = document.getElementById('pause');
const clearBtn = document.getElementById('clear');
const outputEl = document.getElementById('output');
// 打字机核心函数:逐字输出
function typeText() {
if (currentIndex < textBuffer.length && !isPaused) {
// 追加当前字符到输出区域
outputEl.textContent += textBuffer[currentIndex];
currentIndex++;
// 滚动到最底部(避免内容溢出看不到)
outputEl.scrollTop = outputEl.scrollHeight;
// 控制打字速度(可根据需求调整,单位:毫秒)
typingTimer = setTimeout(typeText, 30);
} else if (currentIndex >= textBuffer.length) {
// 文本全部输出完成,清理定时器
clearTimeout(typingTimer);
pauseBtn.disabled = true;
connectBtn.disabled = false;
}
}
// 建立 SSE 连接
function connectSSE() {
// 清空之前的状态
resetState();
try {
// 替换为你的后端 SSE 接口地址
sse = new EventSource('http://localhost:8080/api/ai-stream');
// 监听后端推送的消息(默认事件名 message)
sse.onmessage = function (event) {
// 后端推送的文本内容(需确保后端返回的是纯文本)
const newText = event.data;
if (newText) {
// 将新文本追加到缓冲区
textBuffer += newText;
// 如果还没开始打字,启动打字机
if (!typingTimer && !isPaused) {
typeText();
}
}
};
// 监听连接打开事件
sse.onopen = function () {
console.log('SSE 连接已建立');
connectBtn.disabled = true;
pauseBtn.disabled = false;
};
// 监听错误事件
sse.onerror = function (error) {
console.error('SSE 连接错误:', error);
// 连接异常时关闭 SSE
if (sse.readyState === EventSource.CLOSED) {
resetState();
connectBtn.disabled = false;
pauseBtn.disabled = true;
alert('SSE 连接已断开,请重试');
}
};
// 监听后端自定义的结束事件(可选,需后端配合)
sse.addEventListener('end', function () {
console.log('数据推送完成');
sse.close();
});
} catch (error) {
console.error('创建 SSE 连接失败:', error);
alert('创建 SSE 连接失败,请检查接口地址或网络');
}
}
// 重置所有状态
function resetState() {
// 关闭现有 SSE 连接
if (sse) {
sse.close();
sse = null;
}
// 清理定时器
clearTimeout(typingTimer);
typingTimer = null;
// 重置变量
textBuffer = '';
currentIndex = 0;
isPaused = false;
// 重置 UI
outputEl.textContent = '';
pauseBtn.textContent = '暂停输出';
pauseBtn.disabled = true;
}
// 暂停/继续输出
function togglePause() {
isPaused = !isPaused;
pauseBtn.textContent = isPaused ? '继续输出' : '暂停输出';
// 如果从暂停切换为继续,重新启动打字机
if (!isPaused) {
typeText();
}
}
// 绑定按钮事件
connectBtn.addEventListener('click', connectSSE);
pauseBtn.addEventListener('click', togglePause);
clearBtn.addEventListener('click', resetState);
// 页面关闭时清理 SSE 连接(避免内存泄漏)
window.addEventListener('beforeunload', function () {
if (sse) {
sse.close();
}
});
</script>
</body>
</html>
代码关键说明
- SSE 连接核心:
-
- 使用
new EventSource('后端接口地址')建立 SSE 长连接 onmessage监听后端推送的消息,将文本存入textBuffer缓冲区onopen/onerror处理连接状态,保证异常时能及时重置
- 使用
- 打字机效果:
-
typeText()函数通过setTimeout逐字读取textBuffer并渲染- 可调整
setTimeout的延迟时间(当前 30ms)控制打字速度,值越小速度越快 scrollTop = scrollHeight保证内容始终滚动到最新位置
- 辅助功能:
-
- 暂停 / 继续:通过
isPaused标记控制定时器执行 - 清空:调用
resetState()重置所有状态和 UI - 页面卸载时关闭 SSE 连接,避免无效长连接
- 暂停 / 继续:通过
后端适配说明(关键)
前端代码需要后端配合遵循 SSE 协议返回数据,后端需满足:
- 响应头必须包含:plaintext
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
- 数据格式必须是 SSE 规范格式(每行以
data:开头,空行结束):plaintext
data: 你好
data: 世界
data: [DONE] // 自定义结束标识
- 不要一次性返回所有文本,需分批次推送(模拟 AI 生成过程)
总结
- 核心逻辑:SSE 接收文本存入缓冲区 → 定时器逐字渲染缓冲区内容,实现打字机效果
- 关键配置:可调整
setTimeout延迟时间控制打字速度,替换EventSource地址为实际后端接口 - 异常处理:包含连接错误、页面卸载、输出完成等边界情况,保证功能稳定性
该代码可直接运行,只需替换后端接口地址即可适配你的业务场景,同时保留了暂停、清空等实用功能,满足日常使用需求。