前端web用一个函数实现打字机效果

162 阅读1分钟

前言

  • 最近开发Ai相关项目,要用实现打字机效果,但是呢,后端接口因为一些原因,并非流式,那产品要求又必须是打字机效果,所以只能前端来实现假的SSE流式输出了
  • 在旧项目中看到老代码为了实现一个假的流式输出效果写得特别复杂
  • 而我了解下来,其实一个函数就能实现了,作为笔记记录并分享一些吧

核心代码看这里!!!

/**
* web模拟流式输出效果
* @params {string} msg 文本信息
* @params callback 定时返回追加信息
* @params {{ speedMs: number, indexSpan: number }} speedMs=打字间隔时间(毫秒);indexSpan=每一轮打字的数量
*/
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;
    
    // Update every ~100ms (adjustable)
    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 组件为例看下吧

image.png

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;
    
    // Update every ~100ms (adjustable)
    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}
  />
}