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 = ({ 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 (

);

};

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 =

'2112.1这是智能问答小助手--的响应文本----很长很长的的。

原神*启动!
---王者*启动!react.js 前端 前端框架 react 实现chatGPT的打印机效果 兼容富文本,附git地址  第1张';

return ;

};

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; //光标默认速度=>默认250ms

dieTime?: number; //打字结束后光标消失时间=>默认200ms

blinkSeed?: number; //光标闪烁速度

};

export type TypingEffectType = {

text: string; //文本

seed?: number; //默认打字速度,默认250ms

callback?: () => 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 = /<\/[^>]*>/; //是否是闭合标签 =>true <>=>false

=>false

while (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)) {

// 判断是否为自闭合标签,如



这种不规范的写法

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(null);

const richText =

'原神 · 启动!react.js 前端 前端框架 react 实现chatGPT的打印机效果 兼容富文本,附git地址  第1张



王者荣耀 · 启动!
';

useEffect(() => {

typingEffect(el.current, {

text: richText,

callback: () => {

console.log('打印机结束后执行的回调函数!');

},

cursorConfig: {

cursor: true,

},

});

}, []);

return

;

};

export default Index;

2.4、效果

git项目地址,点我打开

好文阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: