Vue3如何實(shí)現(xiàn)超絲滑打字機(jī)效果組件(可復(fù)用、高定制)
前言
在前端開(kāi)發(fā)中,打字機(jī)效果能極大提升頁(yè)面的交互趣味性和視覺(jué)體驗(yàn),比如 AI 聊天回復(fù)、個(gè)性化介紹頁(yè)等場(chǎng)景都非常適用。本文將分享一個(gè)基于 Vue3 + Composition API 開(kāi)發(fā)的高性能、高定制化打字機(jī)組件,支持打字/刪除循環(huán)、光標(biāo)閃爍、自定義樣式等核心功能,且代碼結(jié)構(gòu)清晰、易于擴(kuò)展。
組件特性
- ? 自定義打字速度、刪除速度、循環(huán)延遲
- ? 支持光標(biāo)顯示/隱藏、閃爍效果開(kāi)關(guān)
- ? 打字完成后自動(dòng)刪除(可選)+ 循環(huán)播放
- ? 完全自定義樣式(字體、顏色、大小等)
- ? 暴露控制方法(開(kāi)始/暫停),支持手動(dòng)干預(yù)
- ? 無(wú)第三方依賴,純?cè)?Vue3 實(shí)現(xiàn)
- ? 性能優(yōu)化:組件卸載自動(dòng)清理定時(shí)器,避免內(nèi)存泄漏

完整組件代碼
<template>
<div class="typewriter-container" :style="fontsConStyle">
<!-- 打字文本 - 逐字符渲染 -->
<span class="typewriter-text">
<span
v-for="(char, index) in displayedText"
:key="index"
class="character"
:data-index="index"
>
{{ char }}
</span>
</span>
<!-- 光標(biāo) - 精準(zhǔn)控制顯示/閃爍 -->
<span
v-if="showCursor && isCursorVisible"
class="cursor"
:class="{ 'blink': showBlinkCursor }"
aria-hidden="true"
/>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, computed, watch, watchEffect } from "vue";
// 組件 Props 定義(帶完整校驗(yàn))
const props = defineProps({
// 要顯示的文本內(nèi)容
text: {
type: String,
required: true,
default: ""
},
// 打字速度(ms/字符)
speed: {
type: Number,
default: 80,
validator: (value) => value > 0
},
// 是否顯示光標(biāo)
showCursor: {
type: Boolean,
default: true
},
// 光標(biāo)是否閃爍
blinkCursor: {
type: Boolean,
default: true
},
// 是否自動(dòng)開(kāi)始打字
autoStart: {
type: Boolean,
default: true
},
// 是否循環(huán)播放
loop: {
type: Boolean,
default: false
},
// 循環(huán)延遲(打字完成后等待時(shí)間)
loopDelay: {
type: Number,
default: 1000,
validator: (value) => value >= 0
},
// 容器樣式(自定義字體、顏色等)
fontsConStyle: {
type: Object,
default: () => ({
fontSize: "2rem",
fontFamily: "'Courier New', monospace",
color: "#333",
lineHeight: "1.5"
})
},
// 是否開(kāi)啟刪除效果
deleteEffect: {
type: Boolean,
default: false
},
// 刪除速度(ms/字符)
deleteSpeed: {
type: Number,
default: 30,
validator: (value) => value > 0
},
// 字符入場(chǎng)動(dòng)畫(huà)開(kāi)關(guān)
charAnimation: {
type: Boolean,
default: true
}
});
// 響應(yīng)式狀態(tài)
const displayedText = ref(""); // 當(dāng)前顯示的文本
const currentIndex = ref(0); // 當(dāng)前字符索引
const isPlaying = ref(false); // 是否正在播放
const isDeleting = ref(false); // 是否正在刪除
const isCursorVisible = ref(true);// 光標(biāo)是否顯示
// 定時(shí)器標(biāo)識(shí)(用于清理)
let intervalId = null;
let timeoutId = null;
let cursorTimer = null;
// 計(jì)算屬性:控制光標(biāo)閃爍狀態(tài)
const showBlinkCursor = computed(() => {
return props.blinkCursor &&
!isDeleting.value &&
(displayedText.value.length === props.text.length || displayedText.value.length === 0);
});
// 工具函數(shù):清除所有定時(shí)器
const clearAllTimers = () => {
if (intervalId) clearInterval(intervalId);
if (timeoutId) clearTimeout(timeoutId);
if (cursorTimer) clearInterval(cursorTimer);
intervalId = null;
timeoutId = null;
cursorTimer = null;
};
// 核心方法:開(kāi)始打字
const startTyping = () => {
// 重置狀態(tài)
clearAllTimers();
isPlaying.value = true;
isDeleting.value = false;
currentIndex.value = 0;
displayedText.value = "";
isCursorVisible.value = true;
// 打字邏輯
intervalId = setInterval(() => {
if (currentIndex.value < props.text.length) {
displayedText.value = props.text.substring(0, currentIndex.value + 1);
currentIndex.value++;
} else {
// 打字完成
clearInterval(intervalId);
isPlaying.value = false;
// 循環(huán)邏輯
if (props.loop) {
if (props.deleteEffect) {
timeoutId = setTimeout(startDeleting, props.loopDelay);
} else {
timeoutId = setTimeout(startTyping, props.loopDelay);
}
}
}
}, props.speed);
};
// 核心方法:開(kāi)始刪除
const startDeleting = () => {
clearAllTimers();
isDeleting.value = true;
intervalId = setInterval(() => {
if (currentIndex.value > 0) {
currentIndex.value--;
displayedText.value = props.text.substring(0, currentIndex.value);
} else {
// 刪除完成
clearInterval(intervalId);
isDeleting.value = false;
// 循環(huán)打字
if (props.loop) {
timeoutId = setTimeout(startTyping, props.loopDelay);
} else {
isCursorVisible.value = false; // 非循環(huán)模式下刪除完成隱藏光標(biāo)
}
}
}, props.deleteSpeed);
};
// 監(jiān)聽(tīng)文本變化:自動(dòng)重啟打字(適配動(dòng)態(tài)文本場(chǎng)景)
watch(() => props.text, (newText) => {
if (newText && props.autoStart) {
startTyping();
}
}, { immediate: true });
// 初始化光標(biāo)閃爍(非閃爍模式下固定顯示)
watchEffect(() => {
if (props.showCursor && !cursorTimer) {
cursorTimer = setInterval(() => {
if (!showBlinkCursor.value) {
isCursorVisible.value = true;
} else {
isCursorVisible.value = !isCursorVisible.value;
}
}, 500);
}
});
// 組件掛載:自動(dòng)開(kāi)始打字
onMounted(() => {
if (props.autoStart && props.text) {
startTyping();
}
});
// 組件卸載:清理所有定時(shí)器(避免內(nèi)存泄漏)
onBeforeUnmount(() => {
clearAllTimers();
});
// 暴露組件方法(供父組件調(diào)用)
defineExpose({
start: startTyping, // 手動(dòng)開(kāi)始
pause: clearAllTimers, // 暫停
restart: () => { // 重啟
clearAllTimers();
startTyping();
},
isPlaying, // 當(dāng)前播放狀態(tài)
isDeleting // 當(dāng)前刪除狀態(tài)
});
</script>
<style scoped>
/* 容器樣式 - 適配行內(nèi)/塊級(jí)顯示 */
.typewriter-container {
display: inline-flex;
align-items: center;
position: relative;
font-size: inherit;
line-height: inherit;
font-family: inherit;
white-space: pre-wrap; /* 支持換行符 */
word-break: break-all; /* 防止長(zhǎng)文本溢出 */
}
/* 文本容器 */
.typewriter-text {
display: inline;
font-size: inherit;
line-height: inherit;
font-family: inherit;
color: inherit;
}
/* 字符樣式 - 入場(chǎng)動(dòng)畫(huà) */
.character {
display: inline-block;
animation: typeIn 0.1s ease-out forwards;
opacity: 0;
}
/* 字符入場(chǎng)動(dòng)畫(huà) */
@keyframes typeIn {
0% {
transform: translateY(5px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
/* 光標(biāo)樣式 - 垂直居中優(yōu)化 */
.cursor {
display: inline-block;
width: 2px;
height: 1.2em; /* 匹配字體高度 */
background-color: currentColor;
margin-left: 2px;
vertical-align: middle;
position: relative;
top: 0;
opacity: 1;
}
/* 光標(biāo)閃爍動(dòng)畫(huà) */
.cursor.blink {
animation: blink 1s infinite step-end;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* 禁用字符動(dòng)畫(huà)的樣式 */
:deep(.character) {
animation: none !important;
opacity: 1 !important;
}
</style>
核心優(yōu)化點(diǎn)說(shuō)明
1. 功能增強(qiáng)
- 新增
charAnimation屬性:可開(kāi)關(guān)字符入場(chǎng)動(dòng)畫(huà),適配不同場(chǎng)景 - 支持動(dòng)態(tài)文本:監(jiān)聽(tīng)
text屬性變化,文本更新自動(dòng)重啟打字 - 優(yōu)化光標(biāo)邏輯:非閃爍模式下光標(biāo)固定顯示,避免閃爍干擾
- 新增
restart方法:支持手動(dòng)重啟打字效果 - 文本換行支持:添加
white-space: pre-wrap,兼容帶換行符的文本
2. 性能優(yōu)化
- 定時(shí)器統(tǒng)一管理:所有定時(shí)器集中清理,避免內(nèi)存泄漏
- 減少不必要渲染:通過(guò)
watchEffect精準(zhǔn)控制光標(biāo)定時(shí)器創(chuàng)建/銷(xiāo)毀 - 樣式優(yōu)化:使用
currentColor繼承文本顏色,光標(biāo)顏色與文本一致 - 邊界處理:添加
word-break: break-all,防止長(zhǎng)文本溢出
3. 代碼健壯性
- 完善 Prop 校驗(yàn):所有數(shù)值類(lèi)型添加范圍校驗(yàn),避免非法值
- 狀態(tài)重置:每次開(kāi)始打字前重置所有狀態(tài),避免多輪執(zhí)行沖突
- 注釋完善:關(guān)鍵邏輯添加注釋,提升代碼可讀性
使用示例
基礎(chǔ)使用
<template>
<Typewriter
text="Hello Vue3! 這是一個(gè)超絲滑的打字機(jī)效果組件?"
speed="50"
/>
</template>
<script setup>
import Typewriter from './components/Typewriter.vue';
</script>
高級(jí)使用(循環(huán)+刪除效果)
<template>
<div>
<Typewriter
ref="typewriterRef"
text="Vue3 打字機(jī)組件 | 支持循環(huán)刪除 | 自定義樣式"
:speed="60"
:deleteSpeed="40"
:loop="true"
:deleteEffect="true"
:loopDelay="1500"
:fontsConStyle="{
fontSize: '1.5rem',
color: '#409eff',
fontFamily: '微軟雅黑'
}"
/>
<button @click="handleRestart">重啟打字</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Typewriter from './components/Typewriter.vue';
const typewriterRef = ref();
// 手動(dòng)重啟打字
const handleRestart = () => {
typewriterRef.value.restart();
};
</script>
樣式自定義說(shuō)明
| 屬性 | 說(shuō)明 | 默認(rèn)值 |
|---|---|---|
| fontSize | 字體大小 | 2rem |
| fontFamily | 字體 | 'Courier New', monospace |
| color | 文本顏色 | #333 |
| lineHeight | 行高 | 1.5 |
你可以通過(guò) fontsConStyle 屬性完全自定義組件樣式,例如:
fontsConStyle: {
fontSize: "18px",
color: "#e6a23c",
fontWeight: "bold",
background: "#f5f7fa",
padding: "10px 15px",
borderRadius: "8px"
}
擴(kuò)展方向
- 自定義字符動(dòng)畫(huà):通過(guò) Prop 傳入動(dòng)畫(huà)類(lèi)名,支持不同的字符入場(chǎng)效果
- 分段打字:支持?jǐn)?shù)組形式的文本,分段打字+間隔
- 速度漸變:實(shí)現(xiàn)打字速度由快到慢/由慢到快的效果
- 暫停/繼續(xù):擴(kuò)展暫停后繼續(xù)打字的功能(記錄當(dāng)前索引)
- 結(jié)合 AI 流式響應(yīng):對(duì)接 AI 接口的流式返回,實(shí)時(shí)更新打字文本
總結(jié)
這個(gè)打字機(jī)組件基于 Vue3 Composition API 開(kāi)發(fā),具備高復(fù)用性、高定制性的特點(diǎn),核心優(yōu)化點(diǎn)如下:
- 完善的定時(shí)器管理,避免內(nèi)存泄漏
- 精準(zhǔn)的狀態(tài)控制,支持打字/刪除/循環(huán)全流程
- 靈活的樣式自定義,適配不同業(yè)務(wù)場(chǎng)景
- 暴露控制方法,支持父組件手動(dòng)干預(yù)
組件可直接集成到 Vue3 項(xiàng)目中,適用于 AI 聊天、個(gè)人主頁(yè)、產(chǎn)品介紹等需要打字機(jī)效果的場(chǎng)景,開(kāi)箱即用!
到此這篇關(guān)于Vue3如何實(shí)現(xiàn)超絲滑打字機(jī)效果組件的文章就介紹到這了,更多相關(guān)Vue3超絲滑打字機(jī)效果組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue組件中點(diǎn)擊按鈕后修改輸入框的狀態(tài)實(shí)例代碼
要求點(diǎn)擊修改按鈕之后部分輸入框由禁用狀態(tài)變?yōu)榭捎脿顟B(tài)。下面我給大家分享一段實(shí)例代碼基于vue組件中點(diǎn)擊按鈕后修改輸入框的狀態(tài),需要的的朋友參考下2017-04-04
配置vite的proxy過(guò)程以及解決post請(qǐng)求403問(wèn)題
文章介紹了Vite項(xiàng)目中本地開(kāi)發(fā)環(huán)境通過(guò)配置proxy代理實(shí)現(xiàn)跨域請(qǐng)求的方法,并解釋了生產(chǎn)環(huán)境中該配置不生效的情況,通常使用nginx轉(zhuǎn)發(fā)或后端配置CORS來(lái)解決,文章還指出,最終的請(qǐng)求地址是target加上rewrite重寫(xiě)的地址2025-12-12
詳解Vue學(xué)習(xí)筆記入門(mén)篇之組件的內(nèi)容分發(fā)(slot)
這篇文章主要介紹了詳解Vue學(xué)習(xí)筆記入門(mén)篇之組件的內(nèi)容分發(fā)(slot),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
vue?parseHTML函數(shù)源碼解析start鉤子函數(shù)
這篇文章主要為大家介紹了vue?parseHTML函數(shù)源碼解析start鉤子函數(shù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
使用webpack打包后的vue項(xiàng)目如何正確運(yùn)行(express)
這篇文章主要介紹了使用webpack打包后的vue項(xiàng)目如何正確運(yùn)行(express) ,接下來(lái)通過(guò)本文給大家詳細(xì)介紹,需要的朋友可以參考下2018-10-10
uni-app自定義導(dǎo)航欄按鈕|uniapp仿微信頂部導(dǎo)航條功能
這篇文章主要介紹了uni-app自定義導(dǎo)航欄按鈕|uniapp仿微信頂部導(dǎo)航條,需要的朋友可以參考下2019-11-11
Vue3?Composition?API優(yōu)雅封裝第三方組件實(shí)例
這篇文章主要為大家介紹了Vue3?Composition?API優(yōu)雅封裝第三方組件實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
vue單行文本溢出會(huì)出現(xiàn)title提示自定義指令
這篇文章主要為大家介紹了vue單行文本溢出會(huì)出現(xiàn)title提示自定義指令,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

