文章分享了如何在前端实现打字机效果,特别是在后端接口不支持流式输出的情况下。作者通过一个简洁的函数`printMessageTimer`,成功模拟了流式输出,并提供了React组件的实现示例。这不仅简化了代码,也提高了用户体验。文章强调了代码的简洁性和实用性,展示了如何用较少的代码实现复杂的功能。
⌨️ 文章的核心在于`printMessageTimer`函数,该函数接受文本信息、回调函数以及控制打字速度和每次显示字符数量的参数。它使用`requestAnimationFrame`实现动画效果,模拟了流式输出。
⚙️ `printMessageTimer`函数内部通过`_index`变量跟踪当前显示的字符位置,`lastTime`变量记录上次更新时间,`deltaTime`计算时间间隔,以控制打字速度。当`_index`超过文本长度时,停止动画。
⚛️ 文章提供了React组件的示例,展示了如何使用`printMessageTimer`函数。组件通过`useEffect`钩子在组件挂载时调用该函数,并将结果传递给`MarkdownRenderer`组件进行渲染,实现了打字机效果。
前言
最近开发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} />}