前言
- 最近开发Ai相关项目,要用实现打字机效果,但是呢,后端接口因为一些原因,并非流式,那产品要求又必须是打字机效果,所以只能前端来实现假的SSE流式输出了
- 在旧项目中看到老代码为了实现一个假的流式输出效果写得特别复杂
- 而我了解下来,其实一个函数就能实现了,作为笔记记录并分享一些吧
核心代码看这里!!!
function printMessageTimer(msg: string, callback: (text: string) => void, { speedMs, indexSpan }: { speedMs: number, indexSpan: number } = { speedMs: 100, indexSpan: 20 }) {
const _msg = msg;
let _index = 0;
let lastTime: number | null = null;
function animate(currentTime: number) {
if (!lastTime) lastTime = currentTime;
const deltaTime = currentTime - lastTime;
if (deltaTime >= speedMs) {
if (_index >= _msg.length) {
callback(_msg);
lastTime = null;
_index = 0;
return;
}
callback(_msg.substring(0, _index) + " [[CURSOR]]");
_index += indexSpan;
lastTime = currentTime;
}
requestAnimationFrame(animate);
}
return function() {
requestAnimationFrame(animate);
};
}
组件使用案例
- 上面提供只是一个核心函数,组件中需要根据实际情况去使用这个函数来更新dom
- 下面以 react 组件为例看下吧

import { useEffect, useState } from 'react'
import MarkdownRenderer from '../components/MarkdownRenderer'
function printMessageTimer(msg: string, callback: (text: string) => void, { speedMs, indexSpan }: { speedMs: number, indexSpan: number } = { speedMs: 100, indexSpan: 20 }) {
const _msg = msg;
let _index = 0;
let lastTime: number | null = null;
function animate(currentTime: number) {
if (!lastTime) lastTime = currentTime;
const deltaTime = currentTime - lastTime;
if (deltaTime >= speedMs) {
if (_index >= _msg.length) {
callback(_msg);
lastTime = null;
_index = 0;
return;
}
callback(_msg.substring(0, _index) + " [[CURSOR]]");
_index += indexSpan;
lastTime = currentTime;
}
requestAnimationFrame(animate);
}
return function() {
requestAnimationFrame(animate);
};
}
export default function AiChatMarkdownCard({ message, animationEnabled=true }: { message: string, animationEnabled?: boolean }) {
const [printMessage, setPrintMessage] = useState('')
useEffect(() => {
if (animationEnabled) {
printMessageTimer(message, setPrintMessage)()
} else {
setPrintMessage(message)
}
}, [])
return <MarkdownRenderer
content={printMessage}
/>
}