Code前端首页关于Code前端联系我们

p>做Vue2项目时,想给文字加打字机效果让页面更生动?但怎么把逐个蹦字的互动感做出来?这篇从基础实现到进阶优化,把原理、代码、细节全拆明白,新手也能跟着做~

terry 6小时前 阅读数 6 #Vue
文章标签 Vue2 打字机效果

为啥Vue2能轻松实现?

打字机效果本质是让文字“一个接一个出现”,背后需要两个关键逻辑:控制文字显示的节奏(每个字啥时候出来)、让页面跟着文字变化更新

Vue2的「数据响应式」正好能解决第二个问题——只要修改绑定的变量,页面会自动更新,所以我们只需要把精力放在「怎么控制每个字出现的节奏」上。

简单说,思路是这样:准备一个「完整文字」和一个「当前已显示文字」,用定时器(setInterval)每次给「当前文字」加一个字,直到把完整文字打完,就像打字员一个字一个字敲,Vue负责把敲好的字显示到页面上~

基础版打字机:3步写出核心效果

先从最基础的版本入手,实现「文字逐字显示」,步骤很清晰:准备数据 → 定时器控制打字 → 模板绑定显示。

定义响应式数据

在Vue组件的data里,存三个关键数据:

  • text:要显示的完整文字(比如'Hello Vue2');
  • typedText当前已打出的文字(初始为空,慢慢拼接);
  • currentIndex:记录当前打到第几个字(初始0,每次加1);
  • timer:定时器标识(用来后续清除,防止内存泄漏)。

代码示例:

data() {
  return {
    text: 'Hello Vue2 打字机效果',
    typedText: '',
    currentIndex: 0,
    timer: null
  }
}

定时器控制「逐字拼接」

mounted钩子(组件渲染完后)里启动定时器,每次循环做这两件事:

  • text的第currentIndex个字符,拼到typedText里;
  • currentIndex1,直到打完所有字,再清除定时器。

代码示例:

mounted() {
  this.startTyping()
},
methods: {
  startTyping() {
    this.timer = setInterval(() => {
      // 判断是否还没打完
      if (this.currentIndex < this.text.length) {
        this.typedText += this.text[this.currentIndex]
        this.currentIndex++
      } else {
        // 打完了,清定时器
        clearInterval(this.timer)
      }
    }, 100) // 每个字间隔100毫秒,可调整速度
  }
}

模板绑定,把效果显示出来

<template>里,把typedText绑定到页面上:

<template>
  <div class="typewriter-box">
    {{ typedText }}
  </div>
</template>

现在运行组件,就能看到文字一个一个蹦出来啦~ 但这版太“机械”,接下来加细节让它更像真实打字机!

细节拉满:让打字效果更「拟真」的3个技巧

真实打字不会每个字间隔完全一样,还会有光标闪烁、偶尔打错删除……加这些细节,互动感直接翻倍!

随机间隔:避免「机器人节奏」

真实打字员的手速有快有慢,所以给每个字的间隔加随机时间差,把固定的100毫秒,改成Math.random()生成的范围值(比如50-150毫秒)。

修改定时器逻辑:

startTyping() {
  this.timer = setInterval(() => {
    if (this.currentIndex < this.text.length) {
      this.typedText += this.text[this.currentIndex]
      this.currentIndex++
    } else {
      clearInterval(this.timer)
    }
  }, Math.random() * 100 + 50) // 间隔在50-150毫秒之间
}

光标闪烁:模拟真实输入框

用CSS动画做一个「竖线光标」,让它一闪一闪,步骤:

  • 给文字容器加伪元素::after设为;
  • 写一个闪烁动画,让伪元素透明度来回切换。

CSS代码:

.typewriter-box {
  position: relative; /* 伪元素要相对定位 */
}
.typewriter-box::after {
  content: '|';
  animation: blink 1s step-end infinite; /* step-end让闪烁更干脆 */
}
@keyframes blink {
  from, to { opacity: 1; }
  50% { opacity: 0; }
}

错误回退:模拟「打错→删除→重打」

真实打字会出错,所以加个“打错后删除重写”的逻辑,思路:

  • mistakeCount记录错误次数;
  • 随机触发错误(比如10%概率),删除最后一个字;
  • 错误后重新开始打这个字。

代码示例(修改startTyping):

data() {
  return {
    // ...其他数据
    mistakeCount: 0 // 新增错误计数
  }
},
methods: {
  startTyping() {
    this.timer = setInterval(() => {
      if (this.currentIndex < this.text.length) {
        // 10%概率触发错误,且错误次数<3次
        if (Math.random() < 0.1 && this.mistakeCount < 3) {
          this.typedText = this.typedText.slice(0, -1) // 删除最后一个字
          this.mistakeCount++
        } else {
          this.typedText += this.text[this.currentIndex]
          this.currentIndex++
          this.mistakeCount = 0 // 没错的话,重置错误计数
        }
      } else {
        clearInterval(this.timer)
      }
    }, 100)
  }
}

加了这三个细节,效果瞬间从“机械打字”变成“真人操作”,用户体验拉满~

组件化封装:项目里随用随拿

如果多个页面需要打字机效果,重复写代码太麻烦,把逻辑封装成可复用组件,传参就能用!

组件设计:Props传参 + 事件通知

  • props:接收要显示的文字text)、打字速度speed)、是否显示光标hasCursor)等;
  • emits:打字完成后,触发finished事件通知父组件。

组件代码(Typewriter.vue):

<template>
  <div class="typewriter" :class="{ 'with-cursor': hasCursor }">
    {{ typedText }}
  </div>
</template>
<script>
export default {
  name: 'Typewriter',
  props: {
    text: { type: String, required: true },
    speed: { type: Number, default: 100 },
    hasCursor: { type: Boolean, default: true }
  },
  data() {
    return {
      typedText: '',
      currentIndex: 0,
      timer: null
    }
  },
  mounted() {
    this.startTyping()
  },
  methods: {
    startTyping() {
      this.timer = setInterval(() => {
        if (this.currentIndex < this.text.length) {
          this.typedText += this.text[this.currentIndex]
          this.currentIndex++
        } else {
          clearInterval(this.timer)
          this.$emit('finished') // 通知父组件“打完了”
        }
      }, this.speed)
    }
  },
  beforeDestroy() {
    clearInterval(this.timer) // 销毁前清定时器
  }
}
</script>
<style scoped>
.typewriter {
  /* 基础样式 */
}
.with-cursor::after {
  content: '|';
  animation: blink 1s step-end infinite;
}
@keyframes blink {
  from, to { opacity: 1; }
  50% { opacity: 0; }
}
</style>

父组件调用:传参+监听事件

在需要的页面引入组件,传textspeed,监听finished事件:

<template>
  <div>
    <Typewriter 
      text="这是封装后の打字效果" 
      :speed="150" 
      @finished="doSomething"
    />
  </div>
</template>
<script>
import Typewriter from './components/Typewriter.vue'
export default {
  components: { Typewriter },
  methods: {
    doSomething() {
      console.log('打字完成!可以做后续操作~')
    }
  }
}
</script>

封装后,不同页面只需一行代码就能实现打字机效果,效率翻倍!

性能优化:长文本/多组件场景不卡壳

如果文字特别长(比如几百字),或者页面有多个打字机组件,容易出现「页面卡顿」,这时候要做性能优化~

定时器必销毁:防止内存泄漏

组件销毁时,定时器还在运行的话,会一直占用内存,甚至触发错误,所以在beforeDestroy钩子手动清除:

beforeDestroy() {
  clearInterval(this.timer)
}

长文本「分片加载」:减少DOM更新频率

如果文字有几百字,逐字更新DOM会很频繁,可以分段加载:比如把文字分成10个字符一段,每段一次性显示,段与段之间间隔稍长。

修改数据和逻辑:

data() {
  return {
    text: '超长文字...'.split(''), // 转成字符数组
    typedText: '',
    currentChunk: 0, // 当前分段索引
    chunkSize: 10, // 每段显示10个字符
    timer: null
  }
},
methods: {
  startTyping() {
    this.timer = setInterval(() => {
      const start = this.currentChunk * this.chunkSize
      const end = start + this.chunkSize
      if (start < this.text.length) {
        // 分段拼接
        this.typedText += this.text.slice(start, end).join('')
        this.currentChunk++
      } else {
        clearInterval(this.timer)
      }
    }, 1000) // 每段间隔1秒,段内字符连续显示
  }
}

这样DOM更新频率从「每个字一次」变成「每10个字一次」,性能友好很多~

玩出花样:和其他交互组合

打字机效果不止「自动播放」,和鼠标、滚动、语音结合,玩法更多!

鼠标悬停触发:hover时打字,离开暂停

给容器绑定@mouseenter@mouseleave事件,控制定时器的启动/暂停:

<template>
  <div 
    class="typewriter" 
    @mouseenter="start" 
    @mouseleave="pause"
  >
    {{ typedText }}
  </div>
</template>
<script>
export default {
  data() {
    return {
      text: '悬停开始打字~',
      typedText: '',
      currentIndex: 0,
      timer: null
    }
  },
  methods: {
    start() {
      if (!this.timer) { // 防止重复启动
        this.timer = setInterval(() => {
          if (this.currentIndex < this.text.length) {
            this.typedText += this.text[this.currentIndex]
            this.currentIndex++
          } else {
            this.pause()
          }
        }, 100)
      }
    },
    pause() {
      clearInterval(this.timer)
      this.timer = null
    }
  }
}
</script>

滚动到元素时自动触发

IntersectionObserver判断「元素是否进入视口」,进入后开始打字:

mounted() {
  const observer = new IntersectionObserver((entries) => {
    if (entries[0].isIntersecting) { // 元素进入视口
      this.startTyping()
      observer.unobserve(this.$el) // 只触发一次
    }
  })
  observer.observe(this.$el) // 监听当前组件的DOM
}

配合语音合成:边打字边读

用浏览器的Web Speech API,每个字显示时播放语音,注意:部分浏览器需要用户交互(比如点击)后才能播放语音,所以实际项目要处理兼容性~

methods: {
  startTyping() {
    this.timer = setInterval(() => {
      if (this.currentIndex < this.text.length) {
        this.typedText += this.text[this.currentIndex]
        // 语音合成:读当前字符
        const utterance = new SpeechSynthesisUtterance(this.text[this.currentIndex])
        speechSynthesis.speak(utterance)
        this.currentIndex++
      } else {
        clearInterval(this.timer)
      }
    }, 100)
  }
}

这些组合玩法能让打字机效果更有创意,适配不同场景~

踩坑指南:效果出不来?这些问题要排查

写代码时遇到「文字直接显示全部」「光标不闪」「定时器关不掉」这些问题?看这里快速排查~

文字没逐字显示,直接蹦出全部

→ 检查定时器逻辑:是不是typedText被直接赋值为text,而不是逐字拼接?比如误写this.typedText = this.text而不是。
→ 检查mounted里有没有调用startTyping方法?漏调的话定时器没启动,文字直接显示。

光标样式不显示

→ 检查CSS:伪元素::aftercontent有没有设为?
→ 检查类名绑定:如果用了hasCursor控制光标,看看class是否正确绑定(比如:class="{ 'with-cursor': hasCursor }")。
→ 检查动画:@keyframes blink是否正确定义,有没有拼写错误?

组件销毁后,打字还在继续

→ 检查beforeDestroy钩子:有没有调用clearInterval(this.timer)?没清的话,定时器会一直运行,甚至引发重复打字。

随机间隔/错误回退没效果

→ 检查逻辑:Math.random()的范围是否正确?比如想做50-150毫秒间隔,是不是写成了Math.random() * 50 + 100(范围变成100-150)?
→ 错误回退的概率和次数限制是否合理?比如Math.random() < 0.01概率太低,肉眼很难看到效果。

遇到问题别慌,把代码拆分成「数据更新→定时器逻辑→DOM渲染」三步,逐步排查就能解决~

从基础实现到细节优化,再到组件化和创意玩法,Vue2做打字机效果的思路其实很清晰——用数据响应式管渲染,用定时器管节奏,只要把这两个核心点吃透,再结合业务场景加细节,就能做出既炫酷又实用的互动效果~ 现在就打开编辑器,把上面的技巧改成自己项目里的代码试试吧~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门