Typewriter 打字器 ✍
📌 Warning
XMarkdown 组件
已经推出,可以和 Typewriter 组件
组合使用,请升级到 beta 1.2.2
版本。
💌 Info
v1.2.0 版本
提供解决 样式覆盖 、渲染图表 以及 自定义代码高亮样式、自定义插件 简单方案
一、我们在组件库新增了 prismjs
官方的 css 样式文件,可以在项目直接引入,解决 md 代码块高亮问题。
二、我们在组件库新增了 Mermaid.js
。用于解决 mermaid 格式
简单的图表渲染问题。
三、我们把 markdown-it
内置的 代码高亮方法 和 插件 暴露出来。方便开发者更好的集成第三方生态的 样式 和 插件
🐵 此温馨提示更新时间:2025-07-06
介绍
Typewriter
是一个可高度定制化开发的 打字器组件
,灵感来自 ant-design-x
官方 气泡组件
案例,将打字方法剥离出来。支持 Markdown 渲染 和 动态打字效果。
代码演示
基本使用
基础用法。
<template>
<ClientOnly>
<Typewriter content="content 属性设置打字器内容" />
</ClientOnly>
</template>
2
3
4
5
Markdown 渲染
通过 isMarkdown
属性控制是否启用 Markdown 渲染模式。
<script setup lang="ts">
const markdownText = ref(
`#### 标题 \n 这是一个 Markdown 示例。\n - 列表项 1 \n - 列表项 2 **粗体文本** 和 *斜体文本* \n \`\`\`javascript \n console.log('Hello, world!'); \n \`\`\``
);
</script>
<template>
<ClientOnly>
<Typewriter :content="markdownText" :is-markdown="true" />
</ClientOnly>
</template>
2
3
4
5
6
7
8
9
10
11
MD-代码块高亮(v1.2.0 新增)
提供一个内置的样式
// 导入 Prism 语法高亮的不同主题样式(基于 vue-element-plus-x 插件提供的样式文件)
// 每个文件对应一种独立的代码高亮主题风格,可根据项目需求选择启用
// 1. Coy 主题(简约浅色风格,适合日常阅读)
import 'vue-element-plus-x/styles/prism-coy.min.css'
// 2. Dark 主题(深色背景主题,适合夜间模式或低光环境)
import 'vue-element-plus-x/styles/prism-dark.min.css'
// 3. Funky 主题(鲜艳色彩风格,代码语法高亮对比强烈)
import 'vue-element-plus-x/styles/prism-funky.min.css'
// 4. Okaidia 主题(深色高对比度主题,注重代码结构区分)
import 'vue-element-plus-x/styles/prism-okaidia.min.css'
// 5. Solarized Light 主题(柔和浅色主题,基于 Solarized 配色方案)
import 'vue-element-plus-x/styles/prism-solarizedlight.min.css'
// 6. Tomorrow 主题(现代简约风格,适合宽屏和大字体显示)
import 'vue-element-plus-x/styles/prism-tomorrow.min.css'
// 7. Twilight 主题(黄昏色调主题,介于明暗之间的平衡风格)
import 'vue-element-plus-x/styles/prism-twilight.min.css'
// 8. Prism 核心基础样式(必须导入,包含语法高亮的基础样式和结构)
import 'vue-element-plus-x/styles/prism.min.css'
/* 使用说明:
1. prism.min.css 是 Prism 的核心样式,包含基本的代码块布局和通用样式,必须保留
2. 其他以 prism-开头的文件是不同的主题样式,可根据项目视觉设计选择 1 个或多个导入
3. 若同时导入多个主题,后导入的样式会覆盖先导入的(可通过切换类名动态切换主题)
4. 主题名称对应 Prism 官方预设主题(如 Coy、Okaidia 等),样式细节可参考 Prism 主题文档
*/
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
<script setup lang="ts">
import { ref } from 'vue';
// import Typewriter from 'vue-element-plus-x/src/components/Typewriter/index.vue';
// import { usePrism } from 'vue-element-plus-x/src/hooks/usePrism.js'
// import AppConfig from 'vue-element-plus-x/src/components/AppConfig/index.vue'
// 这里可以引入 Prism 的核心样式,也可以自己引入其他第三方主题样式
// import 'vue-element-plus-x/styles/prism.min.css';
const markdownText = ref(
`#### 标题 \n 这是一个 Markdown 示例。\n - 列表项 1 \n - 列表项 2 **粗体文本** 和 *斜体文本* \n \`\`\`js \n console.log('Hello, world!'); \n \`\`\``
);
</script>
<template>
<ClientOnly>
<div>
<Typewriter :content="markdownText" :is-markdown="true" />
</div>
</ClientOnly>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MD-插件模式(v1.2.0 新增)
如果你觉得内置的样式不好看或者内置的插件不能满足你的需求,可以通过插件模式自定定义 样式 和 插件。
你也可以自定在 markdown-it
社区中寻找自定义插件,以实现更多自定义功能。
通过 md-plugins
属性,传入 markdown-it
插件数组,即可在 markdown-it
中使用自定义插件。
通过 highlight
函数,传入 Prism 的高亮函数,或者其他高亮库,作用在 markdown-it
中使用 Prism 的高亮功能。
详细 Mermaid 格式 参见:Mermaid.js
<script setup lang="ts">
import markdownItMermaid from '@jsonlee_12138/markdown-it-mermaid';
import { ref } from 'vue';
// 这里是组件库内置的一个 代码高亮库 Prismjs,自定义的 hooks 例子。(仅供集成参考)代码地址:https://github.com/HeJiaYue520/Element-Plus-X/blob/main/packages/components/src/hooks/usePrism.ts
import { usePrism } from 'vue-element-plus-x';
// 这里可以引入 Prism 的核心样式,也可以自己引入其他第三方主题样式
// import 'vue-element-plus-x/styles/prism.min.css';
import 'prismjs/themes/prism.min.css';
const mdPlugins = [markdownItMermaid({ delay: 100, forceLegacyMathML: true })];
const highlight = usePrism();
const markdownText =
ref(`#### 标题 \n 这是一个 Markdown 示例。\n - 列表项 1 \n - 列表项 2 **粗体文本** 和 *斜体文本* \n \`\`\`javascript \n console.log('Hello, world!'); \n \`\`\` \n \`\`\`mermaid
pie title Pets adopted by volunteers
"Dogs" : 386
"Cats" : 85
"Rats" : 15
\n
\`\`\`
\`\`\`mermaid
xychart-beta
title "Sales Revenue"
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
\n
\`\`\`
`);
</script>
<template>
<ClientOnly>
<div>
<Typewriter
:content="markdownText"
:is-markdown="true"
:md-plugins="mdPlugins"
:highlight="highlight"
/>
</div>
</ClientOnly>
</template>
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
开启打字效果
通过 typing
属性控制是否启用 打字渲染模式。typing
也可以是一个对象,设置 step 属性,控制打字每次吐字,interval 属性控制打字间隔,suffix 属性控制打字添加的后缀。
📌 Warning
suffix
属性只能设置字符串,且在 isMarkdown
为 true
时失效,因为后缀会受 markdown
渲染影响,始终会另起一行进行展示,这一点在 ant-design-x
中也会出现。所以我们先暂时决定在 isMarkdown
为 true
时,不展示后缀,让打字器尽可能美观。
<script setup lang="ts">
onMounted(() => {
setContents('text');
setContents('markdown');
});
const isTyping = ref(true);
const content = ref('');
const content1 = ref('');
const markdownText = ref('');
function setContents(type: string) {
if (type === 'text') {
content.value = '';
content1.value = '';
setTimeout(() => {
content.value = 'typing 属性开启打字效果';
content1.value =
'typing 属性也可以是对象,来控制打每次打字吐字、每次打字间隔、和打字器后缀';
}, 800);
}
else if (type === 'markdown') {
markdownText.value = '';
setTimeout(() => {
markdownText.value = ` ### 🐒 is-markdown 和 typing 结合使用 \n 这是一个 Markdown 示例。\n - 列表项 1 \n - 列表项 2 **粗体文本** 和 *斜体文本* \n \`\`\`javascript \n console.log('Hello, world!'); \n \`\`\` `;
}, 800);
}
}
</script>
<template>
<ClientOnly>
<div style="display: flex; flex-direction: column; gap: 8px">
<div>
<el-button style="width: fit-content" @click="setContents('text')">
重置文本
</el-button>
<el-button
style="width: fit-content"
type="primary"
@click="setContents('markdown')"
>
重置 markdown
</el-button>
</div>
<div style="display: flex; gap: 8px; flex-direction: column">
<Typewriter :content="content" :typing="isTyping" />
<Typewriter
:content="content1"
:typing="{ step: 2, interval: 100, suffix: '💩' }"
/>
<Typewriter
:content="markdownText"
:typing="isTyping"
:is-markdown="true"
/>
</div>
</div>
</ClientOnly>
</template>
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
打字器雾化效果
通过 isFog
属性控制是否启用雾化效果。注意,该属性在 isTyping
为 true
时才生效。切回覆盖默认的 typing
后缀属性。
<script setup lang="ts">
const content = ref(
`#### 标题 \n 这是一个 Markdown 示例。\n - 列表项 1 \n - 列表项 2 **粗体文本** 和 *斜体文本* \n \`\`\`javascript \n console.log('Hello, world!'); \n \`\`\``
);
function setContent(type: number) {
content.value = '';
setTimeout(() => {
content.value =
type === 1
? `#### 标题 \n 这是一个 Markdown 示例。\n - 列表项 1 \n - 列表项 2 **粗体文本** 和 *斜体文本* \n \`\`\`javascript \n console.log('Hello, world!'); \n \`\`\``
: '欢迎使用 Element-Plus-X 💖'.repeat(10);
}, 800);
}
</script>
<template>
<ClientOnly>
<div style="display: flex; flex-direction: column; gap: 10px">
<div style="display: flex; gap: 10px">
<el-button @click="setContent(1)">
雾化 Markdown
</el-button>
<el-button @click="setContent(2)">
雾化 文本
</el-button>
</div>
<Typewriter :content="content" :is-markdown="true" is-fog typing />
</div>
</ClientOnly>
</template>
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
动态更新内容
🐒 当使用 typing
属性时,更新 content
如果是之前的子集,则会继续输出,否则会重新输出。
<script setup lang="ts">
const content = ref(
'🥰 感谢使用 Element-Plus-X ! 你的支持,是我们开源的最强动力 ~ '
);
const num = ref(1);
function setContents() {
num.value++;
content.value = content.value.repeat(num.value);
if (num.value > 3) {
num.value = 1;
content.value =
'🥰 感谢使用 Element-Plus-X ! 你的支持,是我们开源的最强动力 ~ ';
}
}
</script>
<template>
<ClientOnly>
<div style="display: flex; flex-direction: column; gap: 10px">
<el-button style="width: fit-content" @click="setContents">
设置 content
</el-button>
<Typewriter typing :content="content" />
</div>
</ClientOnly>
</template>
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
控制打字
💩 更好的控制中断输出、继续打字和销毁等操作
你可以通过组件的 ref
实例获取以下方法和属性:
interrupt
中断打字过程typerRef.interrupt()
continue
继续未完成的打字typerRef.continue()
restart
重新开始打字typerRef.restart()
destroy
销毁组件(清理资源)typerRef.destroy()
renderedContent
获取当前渲染的内容。typerRef.renderedContent.value
isTyping
获取当前是否正在打字。typerRef.isTyping.value
progress
获取当前进度百分比。typerRef.progress.value
💡 Tip
你还可以设置组件的监听事件,获取组件的状态。
@start
打字开始时触发@finish
打字结束时触发@writing
打字时触发
三个方法,默认参数返回组件实例。
<script setup lang="ts">
import type { TypewriterInstance } from 'vue-element-plus-x/types/typewriter';
import {
Delete,
RefreshLeft,
VideoPause,
VideoPlay
} from '@element-plus/icons-vue';
const markdownContent = ref(
`# 🔥 Typewriter 实例方法-事件 \n 😄 使你的打字器可高度定制化。\n - 更方便的控制打字器的状态 \n - 列表项 **粗体文本** 和 *斜体文本* \n \`\`\`javascript \n // 🙉 控制台可以查看相关打日志\n console.log('Hello, world!'); \n \`\`\``
);
const isTypingValue = ref(false);
const progressValue = ref(0);
const typerRef = ref();
// 开始打字的监听方法
function onStart(instance: TypewriterInstance) {
console.log('开始打字:组件 ref 实例', unref(instance));
isTypingValue.value = true;
}
// 打字中,进度监听方法
function onWriting(instance: TypewriterInstance) {
const progress: number = instance.progress.value;
// 避免打印打多次 onWriting 事件 😂
if (progress > 90 && progress < 100) {
// 可以直接获取打字进度,可以根据打字进度,设置更炫酷的样式
// console.log('Writing', `${progress}%`)
console.log(
'打字中 isTyping:',
instance.isTyping.value,
'progress:',
progress
);
}
if (~~progress === 80) {
console.log(
'打字中 progress 为 80% 时候的内容',
instance.renderedContent.value
);
}
isTypingValue.value = true;
progressValue.value = ~~progress; // 通过运算符~~取整 💩
}
// 监听打字结束事件
function onFinish(instance: TypewriterInstance) {
isTypingValue.value = false;
console.log(
'打字结束 isTyping',
instance.isTyping.value,
'progress:',
instance.progress.value
);
}
// 组件实例方法,控制 暂停打字
function onInterrupt() {
typerRef.value.interrupt();
isTypingValue.value = false;
}
function onDestroy() {
typerRef.value.destroy();
isTypingValue.value = false;
progressValue.value = 0;
}
</script>
<template>
<ClientOnly>
<div style="display: flex; flex-direction: column; gap: 12px">
<div style="display: flex">
<el-button
v-if="isTypingValue"
type="warning"
style="width: fit-content"
@click="onInterrupt"
>
<el-icon :size="18">
<VideoPause />
</el-icon>
<span>暂停</span>
</el-button>
<el-button
v-if="!isTypingValue && progressValue !== 0 && progressValue !== 100"
type="success"
style="width: fit-content"
@click="typerRef?.continue()"
>
<el-icon :size="18">
<VideoPlay />
</el-icon>
<span>继续</span>
</el-button>
<el-button
v-if="
!isTypingValue && (progressValue === 0 || progressValue === 100)
"
type="primary"
style="width: fit-content"
@click="typerRef?.restart()"
>
<el-icon :size="18">
<RefreshLeft />
</el-icon>
<span>重播</span>
</el-button>
<el-button type="danger" style="width: fit-content" @click="onDestroy">
<el-icon><Delete /></el-icon>
<span>销毁</span>
</el-button>
</div>
<el-progress
v-if="progressValue > 0 && progressValue !== 100"
:duration="0"
:percentage="progressValue"
/>
<el-progress
v-if="progressValue === 100"
:percentage="100"
status="success"
/>
<!-- 这里展示了如果是 markdown 的话,typing.suffix 会被忽略 -->
<Typewriter
ref="typerRef"
:content="markdownContent"
:typing="{ suffix: '💩', interval: 40 }"
:is-markdown="true"
@start="onStart"
@writing="onWriting"
@finish="onFinish"
/>
</div>
</ClientOnly>
</template>
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
属性
属性名 | 类型 | 是否必填 | 默认值 | 描述 |
---|---|---|---|---|
content | String | 否 | '' | 要展示的文本内容,支持纯文本或 Markdown 格式。 |
isMarkdown | Boolean | 否 | false | 是否启用 Markdown 渲染模式。 |
typing | Boolean | { step?: number, interval?: number, suffix?: string } | 否 | false | 是否启用打字机效果。 |
typing.step | Number | 否 | 2 | 每次打字吐多少字符。 |
typing.interval | Number | 否 | 50 | 每次打字的间隔时间 单位( ms )。 |
typing.suffix | String | 否 | '|' | 打字器后缀光标字符(仅在非 Markdown 模式下生效)。 |
isFog | Boolean | { bgColor?: string, width?: string } | 否 | false | 是否启用雾化效果,可以设置背景色和宽度。 |
事件
事件名 | 参数 | 类型 | 描述 |
---|---|---|---|
@start | ref 实例 | Function | 当打字效果开始时触发 |
@finish | ref 实例 | Function | 当打字效果完成时触发 |
@writing | ref 实例 | Function | 当打字效果进行中不断触发 |
Ref 实例方法
属性名 | 类型 | 描述 |
---|---|---|
interrupt | Function | 中断打字。 |
continue | Function | 继续未完成的打字。 |
restart | Function | 重新开始打字。 |
destroy | Function | 主动销毁打字组件。 |
renderedContent | String | 获取打字组件渲染的内容。 |
isTyping | Boolean | 是否正在打字。 |
progress | Number | 打字进度,取值范围 0 - 100。 |
功能特性
- Markdown 支持:支持渲染 Markdown 格式的文本,并应用 GitHub 风格的样式。
- 动态打字效果:可以模拟打字机的效果,逐步显示文本内容。
- 代码高亮:内置 Prism.js,支持代码块的语法高亮。
- XSS 安全:使用 DOMPurify 对 HTML 内容进行过滤,防止 XSS 攻击。
- 灵活配置:支持自定义打字速度、光标字符、后缀等参数。
- 定制化开发:支持更据组件打字的状态做定制化开发。