react 实现chatGPT的打印机效果 兼容富文本,附git地址

1、方式一 :使用插件 typed.js

typed.js 网站地址,点我打开

1.1、核心代码如下:

//TypeWriteEffect/index.tsx 组件
import React, { useEffect, useRef } from 'react';
import Typed from 'typed.js';
import { PropsType } from './index.d';
const TypeWriteEffect: React.FC<PropsType> = ({ text = '', callback, seed = 20 }) => {const el = useRef(null);useEffect(() => {const typed = new Typed(el.current, {strings: [text],typeSpeed: seed,showCursor: true,onComplete(self) {callback?.();self.cursor.style.display = 'none'; // 隐藏光标},});return () => {typed.destroy();};}, []);return (<div><span ref={el}></span></div>);
};
export default TypeWriteEffect;
// index.d.ts
export type PropsType = {text: string; //文本内容seed?: number; //速度callback?: () => void; //打印结束后的回调函数
};

1.2、使用

/** @Description:* @Author: muge* @LastEditors: muge*/
import TypeWriteEffect from '@/components/TypeWriteEffect';
import React from 'react';const Index = () => {const richText ='<code>2112.1</code>这是<span class="typing-text" style="color: red">智能问答小助手--</span>的响应文本----很长很长的的。<div style="color: pink; font-size: 20px">原神*启动!</div>---王者*启动!<img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0830%2F74168ba1j00rhf6m5002cd000u000jfp.jpg&thumbnail=660x2147483647&quality=80&type=jpg" style="height: 150px"/>';return <TypeWriteEffect text={richText} />;
};
export default Index;

1.3、效果如图

在这里插入图片描述

2、方式二:自定义实现

2.1、思路

我的思路是将字符串切割成两个数组,一个是 <></>的标签数组,一个是按字符和标签截取的数组,效果如图:
在这里插入图片描述
在这里插入图片描述
然后遍历chucksList生成新的数组,如下图:
在这里插入图片描述
然后遍历这个数组,使用定时器插入dom即可

2.2、核心代码

2.2.1、writeEffect.ts

// utils/writeEffect/index.ts
import type { TypingEffectType } from './index.d';
import initData from './lib/tool';
import { createBlinkSpan } from './lib/createBlinkSpan';
import { textConversionArr } from './lib/textConversionArr';
import { getCursorClassName } from './lib/getCursorClassName';
import { removeCursor } from './lib/removeCursor';
/*** @description: 光标打印效果* @param {HTMLElement} dom* @param {TypingEffectType} parameter* @author: muge*/
export const typingEffect = (dom: HTMLElement, parameter: TypingEffectType) => {const { text, callback, cursorConfig = {}, seed = initData.seed } = parameter;const {cursor = false,dieTime = initData.dieTime,blinkSeed = initData.blinkSeed,} = cursorConfig as any;if (!dom || !text) return;const textArrs: string[] = textConversionArr(text);dom.innerHTML = ''; //每次清空内容let blinkInterval: any = null; //光标定时器// 添加光标效果cursor && createBlinkSpan(dom, blinkInterval, blinkSeed);let startIndex = 0;const element = document.createElement('span'); //文本存放标签const start = () => {startIndex++;if (startIndex >= textArrs.length) {cursor && removeCursor(dom, blinkInterval, dieTime);callback?.();return;}if (cursor) {element.innerHTML = textArrs[startIndex];dom.insertBefore(element, getCursorClassName());} else {dom.innerHTML = textArrs[startIndex];}setTimeout(() => start(), seed);};start();
};//index.d.ts
type cursorConfigType = {cursor?: boolean; //是否显示光标seed?: number; //光标默认速度=>默认250msdieTime?: number; //打字结束后光标消失时间=>默认200msblinkSeed?: number; //光标闪烁速度
};
export type TypingEffectType = {text: string; //文本seed?: number; //默认打字速度,默认250mscallback?: () => void; //打字机结束的回调函数cursorConfig?: cursorConfigType; //光标配置项
};

2.2.2、createBlinkSpan

import initData from './tool';export const createBlinkSpan = (dom: HTMLElement,intervalName: NodeJS.Timer,blinkSeed: number,
) => {const { cursorClassName } = initData;const blinkName = document.createElement('span');blinkName.className = cursorClassName;blinkName.innerHTML = '|';dom.appendChild(blinkName);// 设置闪烁间隔,例如每500毫秒切换一次光标状态intervalName = setInterval(() => {blinkName.style.display = blinkName.style.display === 'none' ? 'inline' : 'none';}, blinkSeed);
};

2.2.3、textConversionArr

// 标签切割
const labelCut = (str: string) => {const arrs = str.match(/<[^>]+>(?!\/>)/g);if (!arrs) return [];return arrs.filter((item) => !/<[^>]+\/>$/.test(item));
};
// 通过<></>分隔字符串=》数组
const splitStringToChunks = (str: string): string[] => {const chunks: string[] = [];let currentChunk = '';let insideTag = false;for (let i = 0; i < str.length; i++) {const char = str[i];if (char === '<') {insideTag = true;currentChunk += char;} else if (char === '>') {insideTag = false;currentChunk += char;} else {currentChunk += char;}if (!insideTag || i === str.length - 1) {chunks.push(currentChunk);currentChunk = '';}}return chunks;
};
/*** @description: 文本转换数组* @param {string} str* @author: muge*/
export const textConversionArr = (str: string): string[] => {const labelCutList = labelCut(str);const chucksList = splitStringToChunks(str);let startIndex: number = 0;const result: string[] = [];let lastStr = ''; //拼接的字符串const isCloseTagReg = /<\/[^>]*>/; //是否是闭合标签 </img>=>true  <>=>false <div/>=>falsewhile (startIndex < chucksList?.length) {let currentIndex = startIndex;++startIndex;const currentStr = chucksList[currentIndex];const index = labelCutList.indexOf(currentStr);if (index === -1) {lastStr += currentStr;result.push(lastStr);continue;}// 起始标签if (!/<\/[^>]+>/.test(currentStr)) {// 判断是否为自闭合标签,如 <img> <hr> <br>这种不规范的写法const nextCloseTag: string | undefined = labelCutList[index + 1];if (!nextCloseTag || !isCloseTagReg.test(nextCloseTag)) {lastStr += currentStr;result.push(lastStr);continue;}// 查找第一个闭合标签的下标const findArrs = chucksList.slice(currentIndex);const endTagIndex = findArrs.findIndex((item) => item === nextCloseTag);let curStr: string = '';for (let i = 1; i < endTagIndex; i++) {curStr += findArrs[i];const res = labelCutList[index] + curStr + nextCloseTag;result.push(lastStr + res);if (endTagIndex - 1 === i) {lastStr += res;}}startIndex = currentIndex + endTagIndex; //重置下标continue;}}return result;
};

2.2.4、getCursorClassName

import initData from './tool';
/*** @description: //获取光标dom* @author: muge*/
export const getCursorClassName = () => {return document.querySelector(`.${initData.cursorClassName}`) as HTMLElement;
};

2.2.5、removeCursor

import initData from './tool';
/*** @description: //移除光标标签* @param {HTMLElement} dom //光标标签dom* @param {string} intervalName //定时器名字* @param {number} cursorAway //光标消失时间* @author: muge*/
export const removeCursor = (dom: HTMLElement, intervalName: NodeJS.Timer, cursorAway: number) => {setTimeout(() => {clearInterval(intervalName);dom.removeChild(document.querySelector(`.${initData.cursorClassName}`) as HTMLElement);}, cursorAway);
};

2.2.6、initData

type initDataType = {cursorClassName: string;seed: number;blinkSeed: number;dieTime: number;
};
const initData: initDataType = {cursorClassName: 'blink-class',seed: 100,dieTime: 500,blinkSeed: 350,
};
export default initData;

2.3、使用

import { typingEffect } from '@/utils/writeEffect';
import React, { useEffect, useRef } from 'react';const Index = () => {const el = useRef<HTMLElement | any>(null);const richText ='原神 · 启动!<img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0830%2F74168ba1j00rhf6m5002cd000u000jfp.jpg&thumbnail=660x2147483647&quality=80&type=jpg" style="height: 150px"/><br/><hr><br><div>王者荣耀 · 启动!</div>';useEffect(() => {typingEffect(el.current, {text: richText,callback: () => {console.log('打印机结束后执行的回调函数!');},cursorConfig: {cursor: true,},});}, []);return <div ref={el}></div>;
};export default Index;

2.4、效果

在这里插入图片描述

git项目地址,点我打开

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/183869.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[架构之路-244]:目标系统 - 设计方法 - 软件工程 - 软件开发方法:结构化、面向对象、面向服务、面向组件的开发方法

目录 前言&#xff1a; 一、概述: 软件聚合的程度由简单到复杂 二、主要开发方法详见 2.1 结构化的开发方法 2.2 面对对象的开发方法 2.3 面向服务的开发方法 2.4 面向组件的开发方法 三、不同开发方法比较 3.1 结构化开发方法 3.2 面向对象(OOP)开发方法 3.3 面向服…

java基础练习(使用java实现跨库数据调度ETL)

简介 本文写一篇关于java库与库之间的数据传输&#xff0c;现实生产中可能是通过其他方式完成&#xff0c;在没有架构的基础上使用java实现跨库的数据传送&#xff0c;非常不便利。但是作为练习我觉得确实非常有用&#xff0c;涉及的java知识点相对较多。本文以一个实列讲解&am…

HTTP 协议详解-上(Fiddler 抓包演示)

文章目录 HTTP 协议HTTP 协议的工作过程HTTP 请求 (Request)认识URL关于 URL encode认识 "方法" (method)GET 方法POST 方法其他方法请求 "报头" (header)请求 "正文" (body) HTTP 响应详解状态码响应 "报头" (header) HTTP 协议 HTT…

服务器数据恢复—云服务器mysql数据库表被truncate的数据恢复案例

云服务器数据恢复环境&#xff1a; 阿里云ECS网站服务器&#xff0c;linux操作系统mysql数据库。 云服务器故障&#xff1a; 在执行数据库版本更新测试时&#xff0c;在生产库误执行了本来应该在测试库执行的sql脚本&#xff0c;导致生产库部分表被truncate&#xff0c;还有部…

python 视频硬字幕去除 内嵌字幕去除工具 vsr

项目简介 开源地址&#xff1a;https://github.com/YaoFANGUK/video-subtitle-remover Video-subtitle-remover (VSR) 是一款基于AI技术&#xff0c;将视频中的硬字幕去除的软件。 主要实现了以下功能&#xff1a; 无损分辨率将视频中的硬字幕去除&#xff0c;生成去除字幕后…

大模型的实践应用5-百川大模型(Baichuan-13B)的模型搭建与模型代码详细介绍,以及快速使用方法

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用5-百川大模型(Baichuan-13B)的模型搭建与模型代码详细介绍,以及快速使用方法。 Baichuan-13B 是由百川智能继 Baichuan-7B 之后开发的包含 130 亿参数的开源可商用的大规模语言模型,在权威的中文和英文 benchmark 上均…

25期代码随想录算法训练营第十天 | 栈与队列 part 1

目录 232.用栈实现队列225. 用队列实现栈 232.用栈实现队列 链接 相当于用两个stack将队列的元素顺序颠倒了一遍。 class MyQueue:def __init__(self):self.stack_in []self.stack_out []def push(self, x: int) -> None:self.stack_in.append(x)def pop(self) -> in…

three.js 航拍全景图(+陀螺仪)

右上角陀螺仪也可点击,需要https的环境,手动下载DeviceOrientationControls.js文件 后台包含打点功能 <template><div id"quanjing" style"width: 100vw; height: 100vh; overflow: hidden"><spanid"tip"style"position: ab…

【刷题篇】动态规划(三)

文章目录 1、第 N 个泰波那契数2、三步问题3、使用最小花费爬楼梯4、解码方法5、不同路径6、不同路径 II 1、第 N 个泰波那契数 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c;请返回第 n 个泰波…

【数学】 4、向量的内积、外积、模长

文章目录 一、向量点乘&#xff08;内积&#xff09;1.1 几何意义1.2 点乘的代数定义&#xff0c;推导几何定义&#xff08;用于求向量夹角&#xff09;1.2.1 余弦定理 1.3 程序计算 二、向量叉乘&#xff08;外积&#xff09;2.1 几何意义 三、通俗理解内积和外积四、向量的模…

【网络编程】传输层——TCP协议

文章目录 TCP协议TCP协议格式窗口大小六个标志位确认应答机制超时重传机制连接管理机制三次握手四次挥手 流量控制滑动窗口拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况TCP小结基于TCP的应用层协议TCP与UDP的对比 TCP相关实验CLOSE_WAIT状态实验TIME_WAIT状态实验TI…

gitlab数据备份和恢复

gitlab数据备份 sudo gitlab-rake gitlab:backup:create备份文件默认存放在/var/opt/gitlab/backups路径下&#xff0c; 生成1697101003_2023_10_12_12.0.3-ee_gitlab_backup.tar 文件 gitlab数据恢复 sudo gitlab-rake gitlab:backup:restore BACKUP1697101003_2023_10_12_…

Texlive安装

下载4.8G的iso文件 解压 或 装载后&#xff0c;以管理员身份运行(.bat)文件。 运行以下两句代码进行Texlive相关升级 tlmgr option repository otan tlmgr update --self --all 运行以下三行代码&#xff0c;检查是否安装成功 latex -v xelatex -v pdflatex -v 如果有异常…

Hundred Finance 攻击事件分析

背景知识 Hundred Finance 是 fork Compound 的一个借贷项目&#xff0c;在2023/04/15遭受了黑客攻击。攻击者在发起攻击交易之前执行了两笔准备交易占据了池子&#xff0c;因为发起攻击的前提是池子处于 empty 的状态&#xff08;发行的 hToken 数量为 0&#xff09;。 准备交…

技术分享 | 使用 cURL 发送请求

cURL 是一个通过 URL 传输数据的&#xff0c;功能强大的命令行工具。cURL 可以与 Chrome Devtool 工具配合使用&#xff0c;把浏览器发送的真实请求还原出来&#xff0c;附带认证信息&#xff0c;脱离浏览器执行&#xff0c;方便开发者重放请求、修改参数调试&#xff0c;编写脚…

【GO】项目import第三方的依赖包

目录 一、导入第三方包 1.执行命令 2.查看go环境变量参数 3.查看go.mod文件的变化情况 二、程序里如何import 1. import依赖包 2. 程序编写 本次学习go如果依赖第三方的包&#xff0c;并根据第三方的包提供的接口进行编程&#xff0c;这里需要使用go get命令。下面将go…

个性化联邦学习-综述

介绍阅读的三篇个性化联邦学习的经典综述文章 Three Approaches for Personalization with Applications to Federated Learning 论文地址 文章的主要内容 介绍了用户聚类&#xff0c;数据插值&#xff0c;模型插值三种个性化联邦学习的方法。 用户聚类&#xff1a; 目的&a…

快速解决mfc140u.dll丢失问题,找不到mfc140u.dll修复方法分享

在计算机使用过程中&#xff0c;我们可能会遇到各种问题&#xff0c;其中之一就是某些dll文件丢失。最近&#xff0c;我就遇到了一个关于mfc140u.dll丢失的问题。mfc140u.dll是Microsoft Foundation Class&#xff08;MFC&#xff09;库中的一个动态链接库文件&#xff0c;它包…

Tomcat,jdk下载配置(发布项目)

Tomcat&#xff0c;jdk下载&#xff0c; 远程连接 启动以下服务 高级设置 允许别人连接进来 网上搜索jdk下载即可 双击下一步即可 下一步 输入java&#xff0c;看有没有安装成功 这是安装成功的 Tomcat就可以安装了 和以上操作一样&#xff0c;在网上下载安装包&#xff0c;…

手把手教你搭建属于自己的服务器

最近总是想搭建自己的网站&#xff0c;奈何皮夹里空空如也&#xff0c;服务器也租不起&#xff0c;更别说域名了。于是我就寻思能否自己搭建个服务器&#xff0c;还不要钱呢&#xff1f; 还真行&#xff01;&#xff01;&#xff01; 经过几天的冲浪&#xff0c;我发现有两个…