在现代前端开发中,添加一些视觉效果可以提升用户体验。其中,打字机效果是一种常见且吸引人的效果,可以用于展示动态文本。本文将介绍如何在 Vue 3 中实现打字机效果。
实现步骤
1. 创建自定义指令
我们首先创建一个自定义指令 v-typewriter,用于实现打字机效果。这个指令将逐字显示绑定的文本内容。
javascript
const typeWriter = ref(null);
const typewriterDirective = (el, binding) => {
const indexValue = el.getAttribute('data-index');
const delay = 150; // 设置延迟时间,默认150ms
let i = 0;
typeWriter.value = setInterval(() => {
if (binding?.value && i < binding.value.length) {
if (textList.value && textList.value[indexValue]) {
textList.value[indexValue].typewriterText += binding.value.charAt(i) || '';
}
i++;
} else {
clearInterval(typeWriter.value);
stop(textList.value[indexValue], indexValue, true);
}
}, delay);
};
const vTypewriter = {
mounted(el, binding) {
typewriterDirective(el, binding);
}
};
2. 使用自定义指令
在 Vue 组件中使用自定义指令 v-typewriter。该指令会在元素挂载时自动触发,逐字显示文本内容。
javascript
<template>
<div class="left-content mr-16">
<el-scrollbar ref="scrollRef" height="100%" class="scroll">
<div class="flex mb-48" v-for="(item, index) in textList" :key="item.updateKey">
<div class="user-avatar">
<img v-if="item.resultTts || item.library" src="/img/avatar.png" alt="" />
<img v-else src="/img/user_avatar.png" alt="" />
</div>
<div class="ml-12">
<div class="time mb-11">
<span v-if="item.resultTts || item.library">智能馆员{{ item.time }}</span>
<span v-else>读者{{ item.time }}</span>
</div>
<div>
<div
class="answer"
:class="item.resultTts || item.library ? 'libarary-bg' : 'user-color '"
>
<div v-if="item.resultTts || item.library">
<van-loading v-if="!item.resultMessage" type="spinner" color="#1989fa" />
<div v-if="item.isStop && item.stopText">{{ item.stopText }}</div>
<div
v-if="item.resultTts && !item.isStop"
v-typewriter="item.resultTts"
:data-index="index"
>
{{ item.typewriterText }}
</div>
</div>
<div v-else>{{ item.resultMessage }}</div>
</div>
<div
v-if="(!item.isStop && item.resultTts) || !item.resultMessage"
class="stop-icon mt-18"
@click="stop(item, index)"
>
停止生成
</div>
</div>
<BookList
v-if="item.dataList?.length && item.isStop"
:data-list="item.dataList"
></BookList>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script>
import { ref } from 'vue';
import { useEventBus } from '@/hooks/useEventBus';
const emits = defineEmits(['watchTypeWriter', 'handleStop']);
const textList = ref([]);
useEventBus('clearChatInfo', () => {
textList.value = [];
});
useEventBus('changeAction', (message) => {
// message其它组件传递的数据
clearInterval(typeWriter.value);
if (!textList.value.length || !message.resultTts) {
textList.value.push({
time: ` ${dayjs().format('HH:mm:ss')}`,
isStop: false, // 是否停止
stopText: '', // 打字机停止后的内容
typewriterText: '', // 动态展示打字机内容的文本
updateKey: dayjs().valueOf(), // 每次增加一条数据的唯一key
...message
});
return;
}
if (textList.value.length && message.resultTts) {
textList.value[textList.value.length - 1] = {
...textList.value[textList.value.length - 1],
...message
};
}
});
const stop = (item, index, isFinish = false) => {
if (!item.resultMessage) {
item.isStop = true;
emits('handleStop');
return;
}
if (item.isStop) {
return;
}
const curText = document.querySelector(`[data-index="${index}"]`);
item.isStop = true;
item.stopText = curText?.innerText;
};
</script>
<style scoped lang="scss">
/* 自定义样式*/
</style>