在前端开发中,文字打印动画(Typing Effect) 是一种常见且吸引用户注意力的交互方式。它常用于介绍页、引导语、聊天机器人等场景。
本文实现一个 Vue2 版本的“打字机”效果组件包括:
- 使用
requestAnimationFrame
实现平滑动画 - 控制输出速度和字符块大小
- 生命周期管理与清理
- 与全局状态通信
- 扩展功能建议(如暂停/播放、自动滚动等)
🎯 目标
我们要实现一个如下图效果的组件:
这是一段正在逐字显示的文字...|
其中:
- 文字逐字出现。
- 末尾有一个闪烁光标
|
。 - 支持控制输出速度。
- 可重复播放。
- 支持回调通知完成。
🚀 第一步:搭建基础结构
我们先创建一个最简单的 Vue 组件,展示一段文本。
<template>
<div class="stream-content">
{{ displayText }}
<span class="blinking-cursor">|</span>
</div>
</template>
<script>
export default {
data() {
return {
displayText: ''
};
}
};
</script>
这是静态的展示,接下来我们要让它动起来!
🕒 第二步:添加动画逻辑 —— 模拟“打字”过程
我们使用 JavaScript 的 requestAnimationFrame
方法来实现高帧率更新,模拟逐字输入的效果。
✅ 思路拆解:
- 定义当前已显示的字符索引
currentIndex
。 - 设置每秒输出字符数
speed
和每次输出字符数量chunkSize
。 - 计算每一帧的时间间隔。
- 在每一帧中更新文本内容。
- 当全部输出完毕后触发回调或停止动画。
💡 示例代码片段:
let currentIndex = 0;
const speed = 30; // 字符/秒
const chunkSize = 3;
const interval = 1000 / (speed / chunkSize);
function renderNextChunk(timestamp) {
const elapsed = timestamp - lastRenderTime;
if (elapsed >= interval && currentIndex < fullText.length) {
displayText += fullText.slice(currentIndex, currentIndex + chunkSize);
currentIndex += chunkSize;
lastRenderTime = timestamp;
}
if (currentIndex < fullText.length) {
requestAnimationFrame(renderNextChunk);
}
}
requestAnimationFrame(renderNextChunk);
🧱 第三步:封装为可复用组件(Vue2 版本)
我们将上面的逻辑封装成一个完整的 Vue 组件,支持传参、生命周期管理、以及状态同步。
✨ 最终组件代码(完整版)
下面是最终实现的
StreamDisplay.vue
组件,基于 Vue 2.x 编写。
<template>
<div class="stream-container">
<div class="stream-content">
{{ displayText }}
<span v-if="isPlaying" class="blinking-cursor">|</span>
</div>
</div>
</template>
<script>
export default {
name: 'StreamDisplay',
props: {
fullText: {
type: String,
required: true
},
speed: {
type: Number,
default: 30
},
chunkSize: {
type: Number,
default: 3
},
},
data() {
return {
displayText: '',
isPlaying: false,
currentIndex: 0,
rafId: null,
lastRenderTime: 0
};
},
computed: {
interval() {
return 1000 / (this.speed / this.chunkSize);
}
},
methods: {
renderNextChunk(timestamp) {
if (!this.lastRenderTime) this.lastRenderTime = timestamp;
const elapsed = timestamp - this.lastRenderTime;
if (elapsed >= this.interval && this.currentIndex < this.fullText.length) {
const endIndex = Math.min(this.currentIndex + this.chunkSize, this.fullText.length);
this.displayText += this.fullText.slice(this.currentIndex, endIndex);
this.currentIndex = endIndex;
this.lastRenderTime = timestamp;
}
if (this.currentIndex < this.fullText.length) {
this.rafId = requestAnimationFrame(this.renderNextChunk);
} else {
this.isPlaying = false;
}
},
startStream() {
if (this.currentIndex >= this.fullText.length) {
this.currentIndex = 0;
this.displayText = '';
}
this.isPlaying = true;
this.rafId = requestAnimationFrame(this.renderNextChunk);
},
pauseStream() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
this.isPlaying = false;
}
}
},
watch: {
fullText: {
immediate: true,
handler(newVal) {
if (newVal) {
this.startStream();
}
}
}
},
beforeDestroy() {
this.pauseStream();
}
};
</script>
<style scoped>
.stream-container {
font-family: monospace;
}
.stream-content {
white-space: pre-wrap;
}
.blinking-cursor {
display: inline-block;
width: 1ch;
animation: blink 1s infinite;
}
@keyframes blink {
0%, 50% { opacity: 1; }
50.01%, 100% { opacity: 0; }
}
</style>
🔁 第四步:组件使用示例
你可以像下面这样使用这个组件:
<template>
<div class="app">
<StreamDisplay
:full-text="data"
:speed="20"
:chunk-size="1"
/>
</div>
</template>
<script>
import StreamDisplay from './components/StreamDisplay.vue';
export default {
components: {
StreamDisplay
},
data() {
return {
data: "这是一段测试文本,将会被逐字显示出来..."
};
},
methods: {
handleSetGlobal(prev) {
console.log('打印已完成');
return { ...prev, isStreamFinished: true };
}
}
};
</script>
🛠️ 第五步:深度解析关键知识点
1. requestAnimationFrame
vs setTimeout
requestAnimationFrame
是浏览器提供的动画优化方法,适合做视觉连续更新。- 相比
setTimeout
,它可以根据屏幕刷新率自动调整频率,性能更优。 - 适用于动画、游戏、实时渲染等场景。
2. 数据驱动 vs 响应式更新
- Vue 的响应式系统会自动追踪依赖,在数据变化时更新视图。
- 我们使用
displayText
来驱动 DOM 更新,Vue 自动处理虚拟 DOM diff。
3. 清理副作用
- 在 Vue 中,我们通过
beforeDestroy
钩子取消未完成的动画任务,避免内存泄漏。 - 这是一个良好的编程习惯。
4. 与父级通信
- 使用
props
接收配置项(如fullText
,speed
)。 - 通过
setGlobal
回调传递状态给父组件,可用于后续操作(如跳转页面、切换状态)。
🌟 第六步:扩展功能建议
✅ 添加播放/暂停按钮
你可以添加一个按钮来控制播放状态:
<button @click="isPlaying ? pauseStream() : startStream()">
{{ isPlaying ? '⏸️ 暂停' : '▶️ 开始' }}
</button>
✅ 自动滚动到底部(适合长文本)
如果文本很长,可以监听 renderNextChunk
并自动滚动:
this.$el.scrollIntoView({ behavior: 'smooth', block: 'end' });
✅ 支持富文本格式(HTML)
如果你希望支持 HTML 格式的文本输出,可以使用 v-html
指令:
<div class="stream-content" v-html="displayText"></div>
但要注意 XSS 安全问题。
📌 小结
通过本文你已经掌握了以下技能:
技术点 | 内容 |
---|---|
动画实现 | 使用 requestAnimationFrame 实现流畅打字动画 |
Vue 组件化 | 封装可复用的 Vue2 组件 |
状态管理 | 控制播放状态、进度、完成回调 |
性能优化 | 合理使用帧率计算和资源清理 |
扩展能力 | 支持控制台、滚动、富文本等 |
📚 参考资料
✅ 结语
文字打印机效果虽然简单,但它融合了动画、响应式、组件设计等多个前端核心概念。掌握它不仅有助于理解 Vue 的响应式机制,也能帮助你在实际项目中提升用户体验。
如果你喜欢这篇文章,欢迎点赞、收藏、分享!也欢迎继续关注我,我会带来更多实用的前端开发教程与实战案例。