使用 Vue 3 实现打字机效果

在现代前端开发中,添加一些视觉效果可以提升用户体验。其中,打字机效果是一种常见且吸引人的效果,可以用于展示动态文本。本文将介绍如何在 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>
相关推荐
Monly213 分钟前
Vue:下拉框多选影响行高
前端·javascript·vue.js
是罐装可乐5 分钟前
前端架构知识体系:Source Map 的用法和全面解析
前端·映射·前端架构·代码安全·源码地图·source-map
小桥风满袖11 分钟前
极简三分钟ES6 - ES8中对象扩展
前端·javascript
武昌库里写JAVA19 分钟前
Mac下Python3安装
java·vue.js·spring boot·sql·学习
代码的余温22 分钟前
Web服务器VS应用服务器:核心差异解析
运维·服务器·前端
一涯27 分钟前
日常小常识记录
前端
那一抹阳光多灿烂33 分钟前
CSS 编码规范
前端·css
degree52035 分钟前
CSS :has() 选择器详解:为什么它是“父选择器”?如何实现真正的容器查询?
前端·css·css3
૮・ﻌ・37 分钟前
CSS高级技巧---精灵图、字体图标、布局技巧
前端·css
昔人'37 分钟前
纯`css`固定标题并在滚动时为其添加动画
前端·css