You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js打字机动画代码运行失败求助及优化建议

问题分析与解决方案

报错原因

原代码直接通过document.getElementById("text")获取DOM元素,在Vue项目中如果执行时机早于组件mounted钩子(比如在created阶段或全局代码中),此时目标DOM还未被Vue渲染到页面,会得到null,进而触发Cannot set properties of null (setting 'innerHTML')的报错。同时,直接操作DOM的方式违背了Vue的响应式设计原则,不利于组件的维护和复用。

符合Vue规范的优化方案

改用Vue的响应式数据绑定机制,将动态文本状态交由Vue管理,通过组件生命周期钩子控制动画循环的启动与清理,避免直接操作DOM:

关键改动点

  • 把动态文本内容、循环状态变量(如索引、删除状态)放到组件的响应式数据中
  • 在mounted钩子中启动动画循环,确保DOM已渲染完成
  • 使用Vue的数据绑定更新视图,替代innerHTML操作
  • 保存定时器ID,在组件销毁前清理定时器,防止内存泄漏

完整实现代码

Vue 2 版本

<template>
  <h2>{{ currentPhrase }}</h2>
</template>

<script>
export default {
  data() {
    return {
      phrases: ["I'm John Doe", "I'm student", "I'm developer"],
      currentPhrase: "",
      currentPhraseIndex: 0,
      currentCharacterIndex: 0,
      isDeleting: false,
      timerId: null
    };
  },
  mounted() {
    this.startTypingLoop();
  },
  beforeUnmount() {
    // 组件销毁前清理定时器
    if (this.timerId) {
      clearTimeout(this.timerId);
    }
  },
  methods: {
    startTypingLoop() {
      const currentPhraseText = this.phrases[this.currentPhraseIndex];

      if (!this.isDeleting) {
        this.currentPhrase += currentPhraseText[this.currentCharacterIndex];
        this.currentCharacterIndex++;
      } else {
        this.currentPhrase = this.currentPhrase.slice(0, -1);
        this.currentCharacterIndex--;
      }

      // 切换删除状态或切换短语
      if (this.currentCharacterIndex === currentPhraseText.length) {
        this.isDeleting = true;
      }

      if (this.currentCharacterIndex === 0) {
        this.currentPhrase = "";
        this.isDeleting = false;
        this.currentPhraseIndex++;
        if (this.currentPhraseIndex === this.phrases.length) {
          this.currentPhraseIndex = 0;
        }
      }

      // 计算延迟时间
      const spedUp = Math.random() * (80 - 50) + 50;
      const normalSpeed = Math.random() * (300 - 200) + 200;
      const time = this.isDeleting ? spedUp : normalSpeed;

      // 保存定时器ID,方便后续清理
      this.timerId = setTimeout(() => this.startTypingLoop(), time);
    }
  }
};
</script>

Vue 3 版本(Composition API)

<template>
  <h2>{{ currentPhrase }}</h2>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';

const phrases = ["I'm John Doe", "I'm student", "I'm developer"];
const currentPhrase = ref("");
const currentPhraseIndex = ref(0);
const currentCharacterIndex = ref(0);
const isDeleting = ref(false);
let timerId = null;

function startTypingLoop() {
  const currentPhraseText = phrases[currentPhraseIndex.value];

  if (!isDeleting.value) {
    currentPhrase.value += currentPhraseText[currentCharacterIndex.value];
    currentCharacterIndex.value++;
  } else {
    currentPhrase.value = currentPhrase.value.slice(0, -1);
    currentCharacterIndex.value--;
  }

  if (currentCharacterIndex.value === currentPhraseText.length) {
    isDeleting.value = true;
  }

  if (currentCharacterIndex.value === 0) {
    currentPhrase.value = "";
    isDeleting.value = false;
    currentPhraseIndex.value++;
    if (currentPhraseIndex.value === phrases.length) {
      currentPhraseIndex.value = 0;
    }
  }

  const spedUp = Math.random() * (80 - 50) + 50;
  const normalSpeed = Math.random() * (300 - 200) + 200;
  const time = isDeleting.value ? spedUp : normalSpeed;

  timerId = setTimeout(startTypingLoop, time);
}

onMounted(() => {
  startTypingLoop();
});

onBeforeUnmount(() => {
  if (timerId) {
    clearTimeout(timerId);
  }
});
</script>

额外优化建议

  • 可以将打字动画封装成独立的Vue组件,方便在项目中多处复用
  • 可通过props传入短语列表、打字速度范围等参数,提升组件灵活性
  • 加入动画暂停/继续的控制逻辑,增强交互性

内容的提问来源于stack exchange,提问作者g4rf4z

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:56:04