如何在 Vue 3 中实现打字机效果并让光标闪烁?

来源:前端技术作者:乙爱丽丝头衔:网络博主
导读:本期聚焦于乙爱丽丝创作的《如何在 Vue 3 中实现打字机效果并让光标闪烁?》,敬请观看详情。想在页面里实现逐字输出的文案却不知道从哪下手?Vue 3 的响应式配合定时器就能做出自然的打字机效果。这篇文章会从状态更新入手:用 ref 保存当前已经显示的字数,再通过 setInterval 或 setTimeout 逐字追加,模板只负责渲染切片后的字符串。文中给出最小实现和封装成 useTypewriter 自定义 Hook 的两种方案,同时解释如何控制速度、支持暂停与完成回调。光标闪烁部分不依赖额外库,直接用 CSS 动画在文字末尾画出一条竖线并切换透明度。最后补充 reduceMotion 用户偏好下的降级处理,避免动画给部分访客带来不适。读完可以快速在自己的 Vue 3 项目中加入一个可控、可复用的打字机效果。

在 Vue 3 里做打字机效果,思路其实很直接:别直接操作 DOM,而是让一个响应式状态记录当前已经显示到第几个字符。定时器每次触发时把索引加一,模板再根据这个索引从原始文案里切出子串渲染出来。这样做的好处是 Vue 的更新机制会自然处理界面同步,手动操作 DOM 可能带来的卸载清理、复用问题也都消失了。

如何在 Vue 3 中实现打字机效果并让光标闪烁?

先做最小实现:用 ref 和 setInterval 驱动逐字输出

第一步需要准备三个东西:原始文案、当前已显示字数、以及一个定时器句柄。组合式 API 中可以写成 const fullText = '...'、const visibleText = ref('')、let timer = null。其中 fullText 不需要响应式,因为它不会变;visibleText 必须是 ref,模板里要绑定它;timer 用来在组件卸载时清理,避免内存泄漏。

定时器逻辑可以放在 onMounted 中。每隔一段时间执行一次回调:先判断当前索引是否已经等于原始文案长度,如果相等就清除定时器;否则索引加一,再用 slice(0, index) 更新 visibleText.value。这里用 setInterval 会比递归 setTimeout 简单,但间隔固定时两种差别不大。代码块里是完整的 script setup 写法。

import { ref, onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    const fullText = '这段文字会在页面上逐字显示,模拟打字机效果。';
    const visibleText = ref('');
    let timer = null;
    let index = 0;

    function startTyping() {
      timer = setInterval(function () {
        if (index === fullText.length) {
          clearInterval(timer);
          timer = null;
          return;
        }
        index = index + 1;
        visibleText.value = fullText.slice(0, index);
      }, 120);
    }

    onMounted(startTyping);
    onBeforeUnmount(function () {
      if (timer) {
        clearInterval(timer);
      }
    });

    return { visibleText };
  }
};

对应模板很简单:<p>{{ visibleText }}</p>。这里不需要引入第三方库,也不需要计算属性。因为 visibleText 已经是字符串,直接展示就行。如果你希望保留换行或空格,可以在外面套一个 <pre> 或给元素加 white-space: pre-wrap,避免文本被压缩。

封装成 useTypewriter 自定义 Hook 提高复用性

最小实现适合演示,但在真实项目里一个组件里写满定时器和索引会显得乱。Vue 3 的函数式组合方式很适合把这种逻辑抽成一个 useTypewriter 函数。这个函数接收原始文案和速度参数,返回 visibleText、isDone、reset、pause 等状态和方法。这样任何组件都能按需调用。

封装时需要注意两点。第一,定时器必须独立保存在函数作用域的内部变量里,不能暴露给组件,组件只需要关心返回的控制方法。第二,暂停和继续要能正确清除并重建定时器。下面这个实现用 setInterval 和一个内部索引,同时提供 pause 和 resume。完成时会触发传入的 onDone 回调,并把 isDone 置为 true。

import { ref, readonly } from 'vue';

export function useTypewriter(options) {
  const text = options.text;
  const speed = options.speed || 100;
  const onDone = options.onDone || function () {};

  const visibleText = ref('');
  const isDone = ref(false);
  let timer = null;
  let index = 0;

  function clearTimer() {
    if (timer) {
      clearInterval(timer);
      timer = null;
    }
  }

  function start() {
    clearTimer();
    timer = setInterval(function () {
      if (index === text.length) {
        clearTimer();
        isDone.value = true;
        onDone();
        return;
      }
      index = index + 1;
      visibleText.value = text.slice(0, index);
    }, speed);
  }

  function pause() {
    clearTimer();
  }

  function resume() {
    if (!isDone.value && !timer) {
      start();
    }
  }

  function reset() {
    clearTimer();
    index = 0;
    visibleText.value = '';
    isDone.value = false;
  }

  return {
    visibleText: readonly(visibleText),
    isDone: readonly(isDone),
    start,
    pause,
    resume,
    reset
  };
}

这里把 visibleText 和 isDone 用 readonly 包了一层再返回,是防止使用方在组件里直接改这些状态。调用时只需要 const { visibleText, start, reset } = useTypewriter({ text: '...' }),再在 onMounted 里执行 start 即可。由于 readonly 不影响模板绑定,使用上不会有任何障碍。

这个 Hook 还可以进一步扩展,比如支持动态修改文案后自动重置、支持中英文混合时按字符切分而不是按字节切分。对中文场景来说,JavaScript 的字符串索引和 slice 都按 UTF-16 码元处理,常见汉字没有代理对问题,但如果文案包含 emoji 或特殊符号,建议先转成数组再切片,否则可能出现半个字符。

光标闪烁:用 CSS 动画而不是 JavaScript 定时器

光标闪烁通常只是在文字末尾显示一条竖线,并让它的透明度周期性变化。用 JavaScript 定时器去改样式也能做到,但 CSS 动画更省资源,也更自然。只需要在模板里给文案后面加一个 <span class="cursor"></span>,再用 CSS 定义宽度、颜色和 @keyframes 即可。光标元素可以设置成 inline-block,高度继承文字行高,宽度 2px。

.typewriter-cursor {
  display: inline-block;
  width: 2px;
  height: 1.2em;
  margin-left: 2px;
  background-color: currentColor;
  vertical-align: text-bottom;
  animation: blink 0.8s step-end infinite;
}

@keyframes blink {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
}

CSS 里的 step-end 能让光标在 0 和 1 之间跳变,而不是渐变,看起来更像终端光标。如果不加 step-end,闪烁过程会带半透明过渡,有些人会觉得不够干脆。颜色使用 currentColor 可以跟随文字颜色,避免额外维护一套色彩变量。

模板中只要在 {{ visibleText }} 后面跟一个 <span class="typewriter-cursor"></span>,就能看到光标跟随文字的末尾移动。如果文本很长导致换行,光标也会跟着折行,这是行内元素自然行为。只有需把光标固定在容器右下角时才考虑绝对定位,但这种情况较少见。

处理动画暂停、完成回调和 reduceMotion 降级

打字机效果如果用在首屏或介绍区域,往往会和用户滚动、点击等操作产生重叠。比如用户点击了跳转链接,但动画还在播放,就可能出现文案停留在半截的情况。比较好的做法是在组件卸载前清理定时器,这已经在前面的示例中覆盖。更进一步,当用户触发某个操作时调用 pause,等操作完成再 resume。如果认为动画不重要,也可以直接 reset 后一次性显示全文。

完成的判断不要依赖字符串长度比较可能出现的问题。前面实现里使用索引与总长度相等作为结束条件,这是稳定可靠的。但要注意,如果原始文案为空字符串,应当直接标记完成,不要启动定时器。另外 onDone 回调里如果包含修改其他响应式状态的操作,可能会触发额外的渲染,但不会导致死循环,因为回调只在定时器清理前执行一次。

无障碍方面,可以结合 window.matchMedia('(prefers-reduced-motion: reduce)') 判断用户是否希望减少动画。如果匹配为真,就跳过逐字动画,直接显示完整文案,同时光标闪烁也可以关闭。这个检测可以在 onMounted 中进行,也能写进 useTypewriter 内部,把动画时长设为 0 或直接调用完成逻辑。这样对启用系统减少动效的用户更友好。

还可以给文案容器加 aria-live="polite",让屏幕阅读器在文字变化时播报。对于逐字更新,播报频率可能过于频繁,建议动画结束后再更新一个完整文本的隐藏区域,或在完成时设置 aria-label。这些细节能显著提升可访问性,也是生产级组件容易忽略的地方。

Vue 3打字机效果光标闪烁修改时间:2026-10-06 21:28:12

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1006/66593.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。