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.mzph.cn/news/136165.shtml

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

相关文章

算法进阶指南图论 道路与航线

其实再次看这题的时候。想法就是和强连通分量有关&#xff0c;我们很容易发现&#xff0c;题目中所说的双向边&#xff0c;就构成了一个强连通分量&#xff0c;而所谓的单向边&#xff0c;则相当于把强连通分量进行缩点&#xff0c;然后整个图成为了一个DAG&#xff0c;众所周知…

MongoDB副本集特点验证

MongoDB副本集特点验证 mogodb副本集概述副本集搭建副本集结构验证结果源码地址 mogodb副本集概述 MongoDB副本集是将数据同步在多个服务器的过程。 复制提供了数据的冗余备份&#xff0c;并在多个服务器上存储数据副本&#xff0c;提高了数据的可用性&#xff0c; 并可以保证…

2022ICPC济南站

K Stack Sort 题意&#xff1a;给你一个长度为n的排列&#xff0c;设有m个栈&#xff0c;你需要将这n个数按出现顺序入栈&#xff0c;每次入栈操作从m个栈中选择一个栈从栈顶入栈。当所有元素入栈完成后&#xff0c;需要不断选择栈&#xff0c;将栈中元素弹空。需满足出栈顺序…

【不正经操作】百度深度学习框架paddlepaddle本地运行-Python环境配置笔记

百度深度学习框架PaddlePaddle 百度深度学习框架PaddlePaddle是一个支持深度学习和机器学习的开源框架。它由百度公司于2016年开发并发布&#xff0c;现在已经成为中国最受欢迎的深度学习框架之一&#xff0c;并且在国际上也获得了不少关注。 特点与功能 易于使用 PaddlePa…

ARMday01(计算机理论、ARM理论)

计算机理论 计算机组成 输入设备、输出设备、运算器、控制器、存储器 1.输入设备&#xff1a;将编写好的软件代码以及相关的数据输送到计算机中&#xff0c;转换成计算机能够识别、处理和存储的数据形式 键盘、鼠标、手柄、扫描仪、 2.输出设备&#xff1a;将计算机处理好的数…

解决 github.com 或者 raw.githubusercontent.com 打开慢、打不开问题

系列文章目录 文章目录 系列文章目录前言一、Windows 系统1.1 以管理员身份运行记事本1.2 打开 hosts 文件1.3 写入 IP 地址 二、Linux2.1 hosts 文件位置 三、Android 系统总结 前言 连接手机热点&#xff0c;将流量关闭马上打开&#xff0c;进入的成功率更高 一、Windows 系…

Netty入门指南之NIO 粘包与半包

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Netty应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言问题产…

深度学习读取txt训练数据绘制参数曲线图的方法

有一些深度学习模型是并不像yolo系列那样最终输出相应的参数图&#xff0c;有很多训练形成了一个训练log文件&#xff0c;于是需要读取log文件中的内容并绘制成曲线图。 如下实例&#xff0c;有一个log文件的部分截图&#xff0c;需要将其读取出来并绘制曲线图 废话不多说&…

前端之Bootstrap框架

目录 【一】Bootstrap介绍 【二】Bootstrap引入 【1】CDN加速链接 【2】注意 【三】布局容器 【四】栅格系统 【五】栅格参数 【六】列偏移 【七】排版 标题 内联文本元素 对齐 改变大小写 引用 列表 【八】表格 基本实例 条纹状表格 带边框的表格 鼠标悬停…

汽车工业生产线数字孪生可视化管理平台,赋予工厂车间数字化智慧化管理

在工业4.0 的时代背景下&#xff0c;随着企业数字化进程的推进&#xff0c;数字孪生可视化技术逐渐在汽车行业得到广泛应用&#xff0c;数字孪生智慧工厂的建设也成为了汽车行业数字化转型的趋势之一。汽车制造业属于典型的离散制造行业&#xff0c;汽车生产包含冲压、焊接、涂…

19.13 Boost Asio 发送TCP流数据

Boost框架中默认就提供了针对TCP流传输的支持&#xff0c;该功能可以用来进行基于文本协议的通信&#xff0c;也可以用来实现自定义的协议。一般tcp::iostream会阻塞当前线程&#xff0c;直到IO操作完成。 首先来看服务端代码&#xff0c;如下所示在代码中首先通过GetFileSize…

C++的Odyssey之旅——STL

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; &#x1f354;前言&#xff1a;我们已经将基本语法了解的差不多了&#xff0c;现在我们就该进入C中最重要也是最富有特点的一部分——STL。在学习C语言中我们想要使用顺序表、链表等一些数据结构进行做题时都需要进行这…

Go invalid memory address or nil pointer dereference错误 空指针问题

Go 指针声明后赋值&#xff0c;出现 panic: runtime error: invalid memory address or nil pointer dereference&#xff0c;这种是内存地址错误。 首先我们要了解指针&#xff0c;指针地址在 Go 中 * 代表取指针地址中存的值&#xff0c;& 代表取一个值的地址对于指针&am…

WordPress主题 JustNews主题6.0.1(亲测首页不空白)

介绍 资源入口 需要用WordPress5.X版本 JustNews介绍&#xff1a;一款专为博客、自媒体、资讯类的网站设计开发的WordPress主题&#xff0c;自v3.0版开始支持自主研发的前端用户中心&#xff0c;不仅支持注册、登录、账户设置、个人中心等常用页面的添加&#xff0c;还可以上传…

【原创】java+jsp+servlet简单图书管理系统设计与实现

摘要&#xff1a; 图书管理系统是一个专门针对图书馆管理而设计的系统&#xff0c;它可以帮助图书管理员有效的对图书进行管理&#xff0c;在图书管理系统的设计中&#xff0c;首先要考虑的是系统的需求分析&#xff0c;该系统的设计与实现涉及多个方面&#xff0c;包括数据库…

【赠书第2期】嵌入式虚拟化技术与应用

文章目录 前言 1 背景概述 2 专家推荐 3 本书适合谁&#xff1f; 4 内容简介 5 书籍目录 6 权威作者团队 7 粉丝福利 前言 随着物联网设备的爆炸式增长和万物互联应用的快速发展&#xff0c;虚拟化技术在嵌入式系统上受到了业界越来越多的关注、重视和实际应用。嵌入式…

Linux 入门

Linux 入门 1&#xff1a;linux 用户 root 用户 &#xff1a;也叫超级用户&#xff0c;UID0&#xff0c;其权限最高。系统用户&#xff1a;也叫虚拟用户&#xff0c;UID 1-999普通用户: UID1000-60000, 可以登录系统,操作自己目录下的文件. 1.1:用户操作命令 切换用户: su …

ts面试题总结

文章目录 前言ts和js的区别&#xff1f;什么是Typescript的方法重载&#xff1f;Typescript中never 和 void 的区别&#xff1f;typescript 中的 is 关键字有什么用&#xff1f;TypeScript支持的访问修饰符有哪些&#xff1f;如何定义一个数组&#xff0c;它的元素可能是字符串…

【Mybatis小白从0到90%精讲】12:Mybatis删除 delete, 推荐使用主键删除!

文章目录 前言XML映射文件方式推荐使用主键删除注解方式工具类前言 在实际开发中,我们经常需要删除数据库中的数据,MyBatis可以使用XML映射文件或注解来编写删除(delete)语句,下面是两种方法的示例。 XML映射文件方式 Mapper: int delete(int id);Mapper.xml:

U-Mail信创邮件系统解决方案

近年来&#xff0c;在国家政策的大力引导和自身数字化转型需求驱动下&#xff0c;国产化成为国内数字化发展道路上的关键词&#xff0c;企业不断加强自主创新能力&#xff0c;进行信创建设&#xff0c;实现软硬件系统国产化替代&#xff0c;已成为大势所趋。邮件系统作为企业管…