diff --git a/2018/02/03/hello-world/index.html b/2018/02/03/hello-world/index.html index 2dc6b30..d79470b 100644 --- a/2018/02/03/hello-world/index.html +++ b/2018/02/03/hello-world/index.html @@ -303,7 +303,7 @@

- 17 + 18 日志 diff --git a/2018/02/10/Array_deduplication/index.html b/2018/02/10/Array_deduplication/index.html index 2e17cbc..c31ac30 100644 --- a/2018/02/10/Array_deduplication/index.html +++ b/2018/02/10/Array_deduplication/index.html @@ -302,7 +302,7 @@

- 17 + 18 日志 diff --git a/2018/06/28/chrome_Browser/index.html b/2018/06/28/chrome_Browser/index.html index 75eb8fd..9afa25e 100644 --- a/2018/06/28/chrome_Browser/index.html +++ b/2018/06/28/chrome_Browser/index.html @@ -346,7 +346,7 @@

实践 - 17 + 18 日志 diff --git a/2018/11/15/JS_inherit/index.html b/2018/11/15/JS_inherit/index.html index f3c8d56..16de186 100644 --- a/2018/11/15/JS_inherit/index.html +++ b/2018/11/15/JS_inherit/index.html @@ -328,7 +328,7 @@

总结 - 17 + 18 日志 diff --git a/2019/09/30/ibm_Interview/index.html b/2019/09/30/ibm_Interview/index.html index 1b672cb..4e251db 100644 --- a/2019/09/30/ibm_Interview/index.html +++ b/2019/09/30/ibm_Interview/index.html @@ -310,7 +310,7 @@

- 17 + 18 日志 diff --git a/2019/11/25/JS_copy/index.html b/2019/11/25/JS_copy/index.html index 9db7bfc..9e009f4 100644 --- a/2019/11/25/JS_copy/index.html +++ b/2019/11/25/JS_copy/index.html @@ -338,7 +338,7 @@

- 17 + 18 日志 diff --git a/2019/11/28/this/index.html b/2019/11/28/this/index.html index b49a904..0e9fa4f 100644 --- a/2019/11/28/this/index.html +++ b/2019/11/28/this/index.html @@ -321,7 +321,7 @@

- 17 + 18 日志
diff --git a/2019/12/05/JS_Memory_mechanism/index.html b/2019/12/05/JS_Memory_mechanism/index.html index a5a865a..a9cbea0 100644 --- a/2019/12/05/JS_Memory_mechanism/index.html +++ b/2019/12/05/JS_Memory_mechanism/index.html @@ -367,7 +367,7 @@

- 17 + 18 日志 diff --git a/2020/02/03/Array-From-1/index.html b/2020/02/03/Array-From-1/index.html index f195f8f..8be1d37 100644 --- a/2020/02/03/Array-From-1/index.html +++ b/2020/02/03/Array-From-1/index.html @@ -331,7 +331,7 @@

- 17 + 18 日志 diff --git a/2020/02/03/FE-Study-Again-1/index.html b/2020/02/03/FE-Study-Again-1/index.html index aec9f0c..31748ca 100644 --- a/2020/02/03/FE-Study-Again-1/index.html +++ b/2020/02/03/FE-Study-Again-1/index.html @@ -342,7 +342,7 @@

- 17 + 18 日志 diff --git a/2020/02/03/Linklist-LRU/index.html b/2020/02/03/Linklist-LRU/index.html index 70e7881..5f276cc 100644 --- a/2020/02/03/Linklist-LRU/index.html +++ b/2020/02/03/Linklist-LRU/index.html @@ -376,7 +376,7 @@

- 17 + 18 日志 diff --git "a/2020/02/04/Vue\345\206\205\351\203\250\350\277\220\350\241\214\346\234\272\345\210\266/index.html" "b/2020/02/04/Vue\345\206\205\351\203\250\350\277\220\350\241\214\346\234\272\345\210\266/index.html" index f46b079..cd1b2d6 100644 --- "a/2020/02/04/Vue\345\206\205\351\203\250\350\277\220\350\241\214\346\234\272\345\210\266/index.html" +++ "b/2020/02/04/Vue\345\206\205\351\203\250\350\277\220\350\241\214\346\234\272\345\210\266/index.html" @@ -337,7 +337,7 @@

- 17 + 18 日志 diff --git a/2020/02/05/FE_Intern_think/index.html b/2020/02/05/FE_Intern_think/index.html index 9d98efa..eaf4b5f 100644 --- a/2020/02/05/FE_Intern_think/index.html +++ b/2020/02/05/FE_Intern_think/index.html @@ -339,7 +339,7 @@

结语 - 17 + 18 日志 diff --git a/2020/02/06/sort-num/index.html b/2020/02/06/sort-num/index.html index c9938b8..91a9890 100644 --- a/2020/02/06/sort-num/index.html +++ b/2020/02/06/sort-num/index.html @@ -363,7 +363,7 @@

- 17 + 18 日志 diff --git a/2020/02/07/Chrome-DevTools-Tips/index.html b/2020/02/07/Chrome-DevTools-Tips/index.html index c62212d..9cb42c7 100644 --- a/2020/02/07/Chrome-DevTools-Tips/index.html +++ b/2020/02/07/Chrome-DevTools-Tips/index.html @@ -326,7 +326,7 @@

结语 - 17 + 18 日志 diff --git a/2020/06/21/miniprogram-learn/index.html b/2020/06/21/miniprogram-learn/index.html index d020017..5424986 100644 --- a/2020/06/21/miniprogram-learn/index.html +++ b/2020/06/21/miniprogram-learn/index.html @@ -381,7 +381,7 @@

- 17 + 18 日志 diff --git "a/2022/07/01/\345\244\247\345\255\246\346\257\225\344\270\232\344\270\200\345\271\264\345\220\216\346\224\266\350\216\267\344\270\216\346\204\237\346\202\237/index.html" "b/2022/07/01/\345\244\247\345\255\246\346\257\225\344\270\232\344\270\200\345\271\264\345\220\216\346\224\266\350\216\267\344\270\216\346\204\237\346\202\237/index.html" index 5afe9f3..76f648a 100644 --- "a/2022/07/01/\345\244\247\345\255\246\346\257\225\344\270\232\344\270\200\345\271\264\345\220\216\346\224\266\350\216\267\344\270\216\346\204\237\346\202\237/index.html" +++ "b/2022/07/01/\345\244\247\345\255\246\346\257\225\344\270\232\344\270\200\345\271\264\345\220\216\346\224\266\350\216\267\344\270\216\346\204\237\346\202\237/index.html" @@ -238,7 +238,10 @@

-
+
+
@@ -312,7 +315,7 @@

- 17 + 18 日志
diff --git "a/2024/08/03/markdown\347\211\210\346\211\223\345\255\227\346\234\272\347\273\204\344\273\266\345\256\236\347\216\260/index.html" "b/2024/08/03/markdown\347\211\210\346\211\223\345\255\227\346\234\272\347\273\204\344\273\266\345\256\236\347\216\260/index.html" new file mode 100644 index 0000000..760c723 --- /dev/null +++ "b/2024/08/03/markdown\347\211\210\346\211\223\345\255\227\346\234\272\347\273\204\344\273\266\345\256\236\347\216\260/index.html" @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + markdown版打字机组件实现 | Another Curtin + + + + + + + + + + + + + +
+
+ +
+
+ + + + + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ markdown版打字机组件实现 +

+ + +
+ + + + +
+ + +

随着2024年国内大模型业务需求的发展,前端领域打字机效果组件已广泛应用于多种网站和应用程序中,有效增强了网站的动态性与交互性。

+

本文旨在深入探讨如何从基础开始,逐步开发实现简易版的一个打字机效果的流式组件,包括示例代码及其详细解释。

+ +

效果

Untitled.gif

+

一、初始准备

开始开发前,我们首先需要准备一些基础内容。本次示例中,我们将以一个Markdown文本数据作为打字机效果的内容来源。这不仅能够模拟实际开发中的场景,也便于我们展示如何处理和展示复杂文本。

+

示例Markdown文本

我们的Markdown文本包含了标题、文本格式(如粗体、斜体)、图片和表格等元素,这些都是Markdown常用的标记元素。通过对这些不同类型的内容进行逐字展示,我们可以演示打字机效果处理复杂文本的能力。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
export const mockMarkdownStr = `
# Markdown 流式文章示例
Markdown是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档。Markdown编写的文档后缀为 \`.md\`。在这篇文章中,我将向您展示如何使用Markdown创建一篇包含标题、文本、图片和表格的文章。

## 标题
在Markdown中,标题是通过在文字前面加上 \`#\` 来表示的。\`#\` 的数量代表标题的级别。例如,一个 \`#\` 代表一级标题,两个 \`##\` 代表二级标题,以此类推。

## 文本
Markdown支持普通的文本格式,如粗体、斜体、删除线和下划线。您可以使用以下方式创建这些文本格式:
- **粗体**:使用两个 \`\*\` 或 \`_\` 包围文本,例如 \`\*\*粗体\*\*\` 或 \`__粗体__\`
- *斜体*:使用一个 \`\*\` 或 \`_\` 包围文本,例如 \`\*斜体\*\` 或 \`_斜体_\`
- ~~删除线~~:使用两个 \`~\` 包围文本,例如 \`~~删除线~~\`
- <u>下划线</u>:使用HTML标签 \`<u>\` 和 \`</u>\` 包围文本,例如 \`<u>下划线</u>\`

## 图片
在Markdown中,您可以使用以下语法插入图片:
\`\`\`
![图片描述](图片地址 "可选的标题")
\`\`\`
例如:
\`\`\`
![这是一张小米SU7图片](https://raw.githubusercontent.com/Nonentityboy/PicGoToGitHub/master/su7_1.jpg)
\`\`\`

## 表格
Markdown支持简单的表格创建。您可以使用以下语法创建表格:
\`\`\`
| 标题1 | 标题2 | 标题3 |
|-------|-------|-------|
| 单元格1 | 单元格2 | 单元格3 |
| 单元格4 | 单元格5 | 单元格6 |
\`\`\`
例如:
\`\`\`
| 姓名 | 年龄 | 性别 |
|------|------|------|
| 张三 | 25 | 男 |
| 李四 | 22 | 女 |
\`\`\`

## 结束语
以上是Markdown的基本用法,通过这些简单的标记,您可以创建一篇包含标题、文本、图片和表格的文章。Markdown的语法简单易懂,适合快速排版和分享文档。希望这篇文章对您有所帮助!
`;
+

二、代码实现

实现打字机效果涉及三个部分:

+
    +
  • 核心逻辑TypeWriterCore
  • +
  • Hook封装useTypeWriter
  • +
  • React组件实现TypeWriter Components
    三个部分业务侧可按需取用,以下部分将详细介绍每一部分的实现逻辑及关键代码。
  • +
+

1. TypeWriterCore.ts - 打字机核心逻辑

TypeWriterCore.ts文件中定义了TypeWriterCore类,这个类封装了打字机效果的核心逻辑。通过构造函数,我们可以传入不同的配置选项,如打字速度、暂停时间等,以适应不同的使用场景。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
interface TypeWriterCoreOptions {
onConsume: (str: string) => void; // 定义一个回调函数,用于消费(处理)字符
maxStepSeconds?: number; // 可选属性,定义最大步进间隔(毫秒)
}

export default class TypeWriterCore {
onConsume: (str: string) => void; // 消费(处理)字符的回调函数
queueList: string[] = []; // 存储待消费字符的队列
maxStepSeconds: number = 50; // 默认最大步进间隔为50毫秒
maxQueueNum: number = 2000; // 队列中最大字符数
timer: number | undefined; // 用于控制下一次消费的定时器

constructor({onConsume, maxStepSeconds}: TypeWriterCoreOptions) {
this.onConsume = onConsume; // 初始化消费字符的回调

if (maxStepSeconds !== undefined) {
this.maxStepSeconds = maxStepSeconds; // 如果提供了最大步进间隔,则使用提供的值
}
}

// 动态计算消费字符的速度
dynamicSpeed() {
const speedQueueNum = this.maxQueueNum / this.queueList.length; // 根据队列长度动态调整速度
const resNum = +(
speedQueueNum > this.maxStepSeconds
? this.maxStepSeconds : speedQueueNum
).toFixed(0); // 确保结果为整数

return resNum;
}

// 将字符串添加到队列中
onAddQueueList(str: string) {
this.queueList = [...this.queueList, ...str.split('')]; // 分解字符串为字符数组并追加到队列
}

// 添加字符串到队列的公共方法
add(str: string) {
if (!str) return; // 如果字符串为空,则不执行任何操作
this.onAddQueueList(str); // 调用内部方法添加字符串到队列
}

// 从队列中消费一个字符
consume() {
if (this.queueList.length > 0) {
const str = this.queueList.shift(); // 从队列头部移除一个字符
str && this.onConsume(str); // 如果字符存在,则调用消费函数处理该字符
}
}

// 定时消费队列中的字符
next() {
this.timer = setTimeout(() => {
if (this.queueList.length > 0) {
this.consume(); // 消费一个字符
this.next(); // 递归调用,继续消费下一个字符
}
}, this.dynamicSpeed()); // 根据动态速度设置定时器
}

// 开始消费队列中的字符
start() {
this.next(); // 调用next方法开始消费字符
}

// 渲染完成后的清理工作
onRendered() {
clearTimeout(this.timer); // 清除定时器,防止继续消费字符
}

// 清空队列并停止当前的消费过程
onClearQueueList() {
this.queueList = []; // 清空字符队列
clearTimeout(this.timer); // 清除定时器
}
}
+

2. useTypeWriter.ts - Hook封装

通过HookuseTypeWriter封装TypeWriterCore类,提供简洁的接口,使得在React或Vue组件中易于实现打字机效果。

+

代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import {useEffect, useState, useMemo} from 'react';
import TypeWriterCore from './TypeWriterCore';

interface UseWriterOptions {
maxStepSeconds?: number; // 将 maxStepSeconds 定义为可选的
}

export const useTypeWriter = (
{text, options}:
{ text: string, options?: UseWriterOptions }
) => {
const [typedText, setTypedText] = useState('');

const typingCore = useMemo(
() => new TypeWriterCore(
{
onConsume: (str: string) => setTypedText(prev => prev + str),
...options,
}
),
[]
);

useEffect(
() => {
typingCore.onRendered(); // 渲染完成 => 清空定时器
typingCore.add(text);
typingCore.start();

return () => typingCore.onRendered(); // 渲染完成 => 清空定时器
},
[text]
);

return [typedText];
};

+

3. index.tsx - 组件实现示例

这个文件展示了如何在React组件中使用useTypeWriterHook来实现打字机效果。以下是实现的关键部分:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from 'react';
import ReactMarkdown from 'react-markdown';
import {useTypeWriter} from './useTypeWriter'; // 替换为实际的导入路径
import TypeWriterCore from './TypeWriterCore';


interface TypingWriterProps {
text: string;
options?: {
maxStepSeconds?: number;
};
}

const TypingWriter: React.FC<TypingWriterProps> = ({text, options = {}}) => {
const [typedText] = useTypeWriter({text, options});

return (
<div>
<ReactMarkdown>
{typedText}
</ReactMarkdown>
</div>
);
};


export {
TypingWriter,
TypeWriterCore,
useTypeWriter,
};
+

通过这三个文件的详细解析和代码实现,我们展示了从核心逻辑的构建到在React组件中的应用,如何逐步开发一个打字机效果的流式组件。

+

三、应用示例

1、模拟流式文本消息推送:

以下示例展示如何模拟SSE(Server-Sent Events)文本消息推送,模拟实时数据流的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export const simulateWebSocketPush = (text, onDataReceived) => {
const words = text.split(/([\s,。;:!、])/); // 在标点符号前后插入分隔符,以便保留标点符号
let currentIndex = 0;

// 模拟推送函数
function pushNextChunk() {
const chunkSize = Math.floor(Math.random() * 5) + 1; // 随机生成 1 到 5 的字数
const currentChunk = words.slice(currentIndex, currentIndex + chunkSize).join('');
currentIndex += chunkSize;

// 模拟推送,实际中需要通过 WebSocket 推送给客户端
// 这里将数据通过回调函数传递给调用方
onDataReceived(currentChunk);

// 继续推送,直到所有文字都被推送完
if (currentIndex < words.length) {
const interval = Math.floor(Math.random() * 500) + 1000; // 随机生成时间间隔
setTimeout(pushNextChunk, interval);
}
}

// 开始推送
pushNextChunk();
};

+

2. 使用示例React

此示例展示如何在应用中模拟文本数据传入,实现打字机效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {useEffect, useState} from 'react';
import {TypingWriter} from 'ui-type-writer';
import {simulateWebSocketPush} from '@/utils';
import {mockMarkdownStr} from '@/mock/index'

export default function App() {
const [markdownContent, setMarkdownContent] = useState(' ');

useEffect(
() => {
// 在组件挂载时开始模拟WebSocket推送
simulateWebSocketPush(mockMarkdownStr, data => {
// 这里处理每次推送的数据,可以将数据存储到状态中,或者进行其他操作
// 在每次推送时拼接数据
setMarkdownContent(data);
});
// 清理定时器或其他资源
return () => {};
},
[]
);

return (
<TypingWriter text={markdownContent} />
);
}

+

npm包发布

打字机效果组件已发布到npm,未进行预编译,可以在目录下直接调试。

+
1
npm install  ui-type-writer
+
+

希望本文能为您提供有价值的参考与指导,如有疑问或建议,敬请留言讨论。

+ +
+ + + + + + + +
+ + + + + + +
+ + + + +
+ + + + + + + + +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/about/index.html b/about/index.html index 8a70f6d..b2c1749 100644 --- a/about/index.html +++ b/about/index.html @@ -20,12 +20,12 @@ var CONFIG = {"hostname":"nonentityboy.github.io","root":"/","scheme":"Mist","version":"7.8.0","exturl":false,"sidebar":{"position":"right","display":"always","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}}; - + - + @@ -176,7 +176,7 @@

关于

Hello,我是刘柯廷(Curtin Liu),是一名前端工程师,目前就职于百度。

-

这里是我的个人博客,用来汇总生活、技术上的所思所想。写博客一为记录,二为展示和分享。如果你想要了解更多,下面有关于我的详细介绍,也欢迎与我邮件联系ketingl@foxmail.com

+

这里是我的个人博客,用来汇总生活、技术上的所思所想。写博客一为记录,二为展示和分享。如果你想要了解更多,下面有关于我的详细介绍,也欢迎与我邮件联系ketingl@foxmail.com

详细介绍

diff --git a/archives/2018/02/index.html b/archives/2018/02/index.html index 6dca1e6..dc07ebf 100644 --- a/archives/2018/02/index.html +++ b/archives/2018/02/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -278,7 +278,7 @@

Another Curtin

diff --git a/archives/2018/06/index.html b/archives/2018/06/index.html index dea713d..73a07e8 100644 --- a/archives/2018/06/index.html +++ b/archives/2018/06/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -258,7 +258,7 @@

Another Curtin

diff --git a/archives/2018/11/index.html b/archives/2018/11/index.html index 566e809..ef8be56 100644 --- a/archives/2018/11/index.html +++ b/archives/2018/11/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -258,7 +258,7 @@

Another Curtin

diff --git a/archives/2018/index.html b/archives/2018/index.html index bc4ee75..f9a3a11 100644 --- a/archives/2018/index.html +++ b/archives/2018/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -318,7 +318,7 @@

Another Curtin

diff --git a/archives/2019/09/index.html b/archives/2019/09/index.html index dfb7ed7..0f5dd7a 100644 --- a/archives/2019/09/index.html +++ b/archives/2019/09/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -258,7 +258,7 @@

Another Curtin

diff --git a/archives/2019/11/index.html b/archives/2019/11/index.html index e7fc68c..cd6c988 100644 --- a/archives/2019/11/index.html +++ b/archives/2019/11/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -278,7 +278,7 @@

Another Curtin

diff --git a/archives/2019/12/index.html b/archives/2019/12/index.html index 1cedfc6..13eb671 100644 --- a/archives/2019/12/index.html +++ b/archives/2019/12/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -258,7 +258,7 @@

Another Curtin

diff --git a/archives/2019/index.html b/archives/2019/index.html index 4cea13f..30c186f 100644 --- a/archives/2019/index.html +++ b/archives/2019/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -318,7 +318,7 @@

Another Curtin

diff --git a/archives/2020/02/index.html b/archives/2020/02/index.html index 3274a9d..8f9fd89 100644 --- a/archives/2020/02/index.html +++ b/archives/2020/02/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -378,7 +378,7 @@

Another Curtin

diff --git a/archives/2020/06/index.html b/archives/2020/06/index.html index b18e527..600136e 100644 --- a/archives/2020/06/index.html +++ b/archives/2020/06/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -258,7 +258,7 @@

Another Curtin

diff --git a/archives/2020/index.html b/archives/2020/index.html index c58108a..cb131c6 100644 --- a/archives/2020/index.html +++ b/archives/2020/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -398,7 +398,7 @@

Another Curtin

diff --git a/archives/2022/07/index.html b/archives/2022/07/index.html index 3836777..390bf70 100644 --- a/archives/2022/07/index.html +++ b/archives/2022/07/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -258,7 +258,7 @@

Another Curtin

diff --git a/archives/2022/index.html b/archives/2022/index.html index b24a0d9..0fc3516 100644 --- a/archives/2022/index.html +++ b/archives/2022/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -258,7 +258,7 @@

Another Curtin

diff --git a/archives/2024/08/index.html b/archives/2024/08/index.html new file mode 100644 index 0000000..87e05b7 --- /dev/null +++ b/archives/2024/08/index.html @@ -0,0 +1,362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Another Curtin + + + + + + + + + + + + + +
+
+ +
+
+ + + + + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 18 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/index.html b/archives/2024/index.html new file mode 100644 index 0000000..b8cf39f --- /dev/null +++ b/archives/2024/index.html @@ -0,0 +1,362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | Another Curtin + + + + + + + + + + + + + +
+
+ +
+
+ + + + + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 18 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/index.html b/archives/index.html index 1a4f84f..4d1a89d 100644 --- a/archives/index.html +++ b/archives/index.html @@ -156,10 +156,33 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
+
+ 2024 +
+ +
2022
@@ -346,29 +369,6 @@

Another Curtin

-
- 2019 -
- -
@@ -447,7 +447,7 @@

Another Curtin

diff --git a/archives/page/2/index.html b/archives/page/2/index.html index 18221fa..9a73763 100644 --- a/archives/page/2/index.html +++ b/archives/page/2/index.html @@ -156,7 +156,7 @@

Another Curtin

- 嗯..! 目前共计 17 篇日志。 继续努力。 + 嗯..! 目前共计 18 篇日志。 继续努力。
@@ -164,6 +164,26 @@

Another Curtin

2019
+ +
@@ -384,7 +404,7 @@

Another Curtin

diff --git a/atom.xml b/atom.xml index 69412ee..517d5e3 100644 --- a/atom.xml +++ b/atom.xml @@ -6,7 +6,7 @@ - 2023-03-17T15:40:05.570Z + 2024-08-03T14:51:52.276Z https://nonentityboy.github.io/ @@ -16,6 +16,24 @@ Hexo + + markdown版打字机组件实现 + + https://nonentityboy.github.io/2024/08/03/markdown%E7%89%88%E6%89%93%E5%AD%97%E6%9C%BA%E7%BB%84%E4%BB%B6%E5%AE%9E%E7%8E%B0/ + 2024-08-03T14:39:26.000Z + 2024-08-03T14:51:52.276Z + + 随着2024年国内大模型业务需求的发展,前端领域打字机效果组件已广泛应用于多种网站和应用程序中,有效增强了网站的动态性与交互性。

本文旨在深入探讨如何从基础开始,逐步开发实现简易版的一个打字机效果的流式组件,包括示例代码及其详细解释。

效果

Untitled.gif

一、初始准备

开始开发前,我们首先需要准备一些基础内容。本次示例中,我们将以一个Markdown文本数据作为打字机效果的内容来源。这不仅能够模拟实际开发中的场景,也便于我们展示如何处理和展示复杂文本。

示例Markdown文本

我们的Markdown文本包含了标题、文本格式(如粗体、斜体)、图片和表格等元素,这些都是Markdown常用的标记元素。通过对这些不同类型的内容进行逐字展示,我们可以演示打字机效果处理复杂文本的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
export const mockMarkdownStr = `
# Markdown 流式文章示例
Markdown是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档。Markdown编写的文档后缀为 \`.md\`。在这篇文章中,我将向您展示如何使用Markdown创建一篇包含标题、文本、图片和表格的文章。

## 标题
在Markdown中,标题是通过在文字前面加上 \`#\` 来表示的。\`#\` 的数量代表标题的级别。例如,一个 \`#\` 代表一级标题,两个 \`##\` 代表二级标题,以此类推。

## 文本
Markdown支持普通的文本格式,如粗体、斜体、删除线和下划线。您可以使用以下方式创建这些文本格式:
- **粗体**:使用两个 \`\*\` 或 \`_\` 包围文本,例如 \`\*\*粗体\*\*\` 或 \`__粗体__\`
- *斜体*:使用一个 \`\*\` 或 \`_\` 包围文本,例如 \`\*斜体\*\` 或 \`_斜体_\`
- ~~删除线~~:使用两个 \`~\` 包围文本,例如 \`~~删除线~~\`
- <u>下划线</u>:使用HTML标签 \`<u>\` 和 \`</u>\` 包围文本,例如 \`<u>下划线</u>\`

## 图片
在Markdown中,您可以使用以下语法插入图片:
\`\`\`
![图片描述](图片地址 "可选的标题")
\`\`\`
例如:
\`\`\`
![这是一张小米SU7图片](https://raw.githubusercontent.com/Nonentityboy/PicGoToGitHub/master/su7_1.jpg)
\`\`\`

## 表格
Markdown支持简单的表格创建。您可以使用以下语法创建表格:
\`\`\`
| 标题1 | 标题2 | 标题3 |
|-------|-------|-------|
| 单元格1 | 单元格2 | 单元格3 |
| 单元格4 | 单元格5 | 单元格6 |
\`\`\`
例如:
\`\`\`
| 姓名 | 年龄 | 性别 |
|------|------|------|
| 张三 | 25 | 男 |
| 李四 | 22 | 女 |
\`\`\`

## 结束语
以上是Markdown的基本用法,通过这些简单的标记,您可以创建一篇包含标题、文本、图片和表格的文章。Markdown的语法简单易懂,适合快速排版和分享文档。希望这篇文章对您有所帮助!
`;

二、代码实现

实现打字机效果涉及三个部分:

  • 核心逻辑TypeWriterCore
  • Hook封装useTypeWriter
  • React组件实现TypeWriter Components
    三个部分业务侧可按需取用,以下部分将详细介绍每一部分的实现逻辑及关键代码。

1. TypeWriterCore.ts - 打字机核心逻辑

TypeWriterCore.ts文件中定义了TypeWriterCore类,这个类封装了打字机效果的核心逻辑。通过构造函数,我们可以传入不同的配置选项,如打字速度、暂停时间等,以适应不同的使用场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
interface TypeWriterCoreOptions {
onConsume: (str: string) => void; // 定义一个回调函数,用于消费(处理)字符
maxStepSeconds?: number; // 可选属性,定义最大步进间隔(毫秒)
}

export default class TypeWriterCore {
onConsume: (str: string) => void; // 消费(处理)字符的回调函数
queueList: string[] = []; // 存储待消费字符的队列
maxStepSeconds: number = 50; // 默认最大步进间隔为50毫秒
maxQueueNum: number = 2000; // 队列中最大字符数
timer: number | undefined; // 用于控制下一次消费的定时器

constructor({onConsume, maxStepSeconds}: TypeWriterCoreOptions) {
this.onConsume = onConsume; // 初始化消费字符的回调

if (maxStepSeconds !== undefined) {
this.maxStepSeconds = maxStepSeconds; // 如果提供了最大步进间隔,则使用提供的值
}
}

// 动态计算消费字符的速度
dynamicSpeed() {
const speedQueueNum = this.maxQueueNum / this.queueList.length; // 根据队列长度动态调整速度
const resNum = +(
speedQueueNum > this.maxStepSeconds
? this.maxStepSeconds : speedQueueNum
).toFixed(0); // 确保结果为整数

return resNum;
}

// 将字符串添加到队列中
onAddQueueList(str: string) {
this.queueList = [...this.queueList, ...str.split('')]; // 分解字符串为字符数组并追加到队列
}

// 添加字符串到队列的公共方法
add(str: string) {
if (!str) return; // 如果字符串为空,则不执行任何操作
this.onAddQueueList(str); // 调用内部方法添加字符串到队列
}

// 从队列中消费一个字符
consume() {
if (this.queueList.length > 0) {
const str = this.queueList.shift(); // 从队列头部移除一个字符
str && this.onConsume(str); // 如果字符存在,则调用消费函数处理该字符
}
}

// 定时消费队列中的字符
next() {
this.timer = setTimeout(() => {
if (this.queueList.length > 0) {
this.consume(); // 消费一个字符
this.next(); // 递归调用,继续消费下一个字符
}
}, this.dynamicSpeed()); // 根据动态速度设置定时器
}

// 开始消费队列中的字符
start() {
this.next(); // 调用next方法开始消费字符
}

// 渲染完成后的清理工作
onRendered() {
clearTimeout(this.timer); // 清除定时器,防止继续消费字符
}

// 清空队列并停止当前的消费过程
onClearQueueList() {
this.queueList = []; // 清空字符队列
clearTimeout(this.timer); // 清除定时器
}
}

2. useTypeWriter.ts - Hook封装

通过HookuseTypeWriter封装TypeWriterCore类,提供简洁的接口,使得在React或Vue组件中易于实现打字机效果。

代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import {useEffect, useState, useMemo} from 'react';
import TypeWriterCore from './TypeWriterCore';

interface UseWriterOptions {
maxStepSeconds?: number; // 将 maxStepSeconds 定义为可选的
}

export const useTypeWriter = (
{text, options}:
{ text: string, options?: UseWriterOptions }
) => {
const [typedText, setTypedText] = useState('');

const typingCore = useMemo(
() => new TypeWriterCore(
{
onConsume: (str: string) => setTypedText(prev => prev + str),
...options,
}
),
[]
);

useEffect(
() => {
typingCore.onRendered(); // 渲染完成 => 清空定时器
typingCore.add(text);
typingCore.start();

return () => typingCore.onRendered(); // 渲染完成 => 清空定时器
},
[text]
);

return [typedText];
};

3. index.tsx - 组件实现示例

这个文件展示了如何在React组件中使用useTypeWriterHook来实现打字机效果。以下是实现的关键部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from 'react';
import ReactMarkdown from 'react-markdown';
import {useTypeWriter} from './useTypeWriter'; // 替换为实际的导入路径
import TypeWriterCore from './TypeWriterCore';


interface TypingWriterProps {
text: string;
options?: {
maxStepSeconds?: number;
};
}

const TypingWriter: React.FC<TypingWriterProps> = ({text, options = {}}) => {
const [typedText] = useTypeWriter({text, options});

return (
<div>
<ReactMarkdown>
{typedText}
</ReactMarkdown>
</div>
);
};


export {
TypingWriter,
TypeWriterCore,
useTypeWriter,
};

通过这三个文件的详细解析和代码实现,我们展示了从核心逻辑的构建到在React组件中的应用,如何逐步开发一个打字机效果的流式组件。

三、应用示例

1、模拟流式文本消息推送:

以下示例展示如何模拟SSE(Server-Sent Events)文本消息推送,模拟实时数据流的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export const simulateWebSocketPush = (text, onDataReceived) => {
const words = text.split(/([\s,。;:!、])/); // 在标点符号前后插入分隔符,以便保留标点符号
let currentIndex = 0;

// 模拟推送函数
function pushNextChunk() {
const chunkSize = Math.floor(Math.random() * 5) + 1; // 随机生成 1 到 5 的字数
const currentChunk = words.slice(currentIndex, currentIndex + chunkSize).join('');
currentIndex += chunkSize;

// 模拟推送,实际中需要通过 WebSocket 推送给客户端
// 这里将数据通过回调函数传递给调用方
onDataReceived(currentChunk);

// 继续推送,直到所有文字都被推送完
if (currentIndex < words.length) {
const interval = Math.floor(Math.random() * 500) + 1000; // 随机生成时间间隔
setTimeout(pushNextChunk, interval);
}
}

// 开始推送
pushNextChunk();
};

2. 使用示例React

此示例展示如何在应用中模拟文本数据传入,实现打字机效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {useEffect, useState} from 'react';
import {TypingWriter} from 'ui-type-writer';
import {simulateWebSocketPush} from '@/utils';
import {mockMarkdownStr} from '@/mock/index'

export default function App() {
const [markdownContent, setMarkdownContent] = useState(' ');

useEffect(
() => {
// 在组件挂载时开始模拟WebSocket推送
simulateWebSocketPush(mockMarkdownStr, data => {
// 这里处理每次推送的数据,可以将数据存储到状态中,或者进行其他操作
// 在每次推送时拼接数据
setMarkdownContent(data);
});
// 清理定时器或其他资源
return () => {};
},
[]
);

return (
<TypingWriter text={markdownContent} />
);
}

npm包发布

打字机效果组件已发布到npm,未进行预编译,可以在目录下直接调试。

1
npm install  ui-type-writer

希望本文能为您提供有价值的参考与指导,如有疑问或建议,敬请留言讨论。

]]>
+ + + <p>随着2024年国内大模型业务需求的发展,前端领域打字机效果组件已广泛应用于多种网站和应用程序中,有效增强了网站的动态性与交互性。</p> +<p>本文旨在深入探讨如何从基础开始,逐步开发实现简易版的一个打字机效果的流式组件,包括示例代码及其详细解释。</p> + + + + +
+ 大学毕业一年后收获与感悟 diff --git a/index.html b/index.html index 52e3311..79436f7 100644 --- a/index.html +++ b/index.html @@ -152,6 +152,73 @@

Another Curtin

+
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

随着2024年国内大模型业务需求的发展,前端领域打字机效果组件已广泛应用于多种网站和应用程序中,有效增强了网站的动态性与交互性。

+

本文旨在深入探讨如何从基础开始,逐步开发实现简易版的一个打字机效果的流式组件,包括示例代码及其详细解释。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + +
+
+
+
+ + + + + + +
@@ -775,72 +842,6 @@

- - - -
- - - - - -
-

- - -

- - -
- - - - -
- - -

先抛出一个问题?JS中数据如何存储。
基本数据类型用栈存储,引用数据类型用堆存储。
看起来没毛病,实际上是需要考虑闭包的情况。如果变量存在栈中,函数调用完栈顶空间销毁,闭包变量(闭包变量存在堆内存中)不就没了吗?

- -
- - 阅读全文 » - -
- - - -
- - - - -
-
-
-
- - - -