导读:本期聚焦于乙爱丽丝创作的《如何用jQuery实现打字机效果并让HTML标签整块输出不被逐字打散?》,敬请观看详情。打字机效果是网页中常见的动态展示手段,但直接对包含HTML标签的内容做逐字输出时,标签会被拆散导致页面结构错乱。本文介绍一种用jQuery实现的Typewriter方案,核心思路是先把内容按节点拆分,文本节点逐字追加,标签节点整体插入,从而既保留视觉效果又维护DOM结构完整。文中还包含光标闪烁、速度控制、循环播放以及多元素依次打字等常见需求的实现细节,并对比了定时器setTimeout与requestAnimationFrame两种驱动方式的差异,帮助你快速在落地页、企业站首页或个人主页中套用这套代码。

打字机效果(Typewriter)在落地页标题、企业官网首页的文案展示、命令行风格的终端演示中出镜率非常高。它的实现原理并不复杂:把一段文字按字符依次插入页面,配合一个闪烁的光标,就能模拟出人在敲键盘的过程。真正麻烦的地方在于,要输出的内容往往不只是纯文本,而是带着加粗强调甚至换行标签的富文本。如果直接用substring截取HTML字符串再塞进容器,标签会被拦腰截断,页面直接报错或者渲染出混乱的结构。本文就来解决这个问题,给出一份可直接落地、支持HTML标签整块输出的jQuery打字机实现。

如何用jQuery实现打字机效果并让HTML标签整块输出不被逐字打散?

一、为什么直接截取HTML字符串会出问题

不少人在第一版实现里会写出类似这样的代码:每隔一段时间,用substring(0, i)截取原始HTML,然后html()写回容器。如果内容是纯文本,这样做完全没问题,因为每一个字符都是独立可显示的。但一旦内容里出现<strong>这类标签,截断点落在标签中间时,写入的就是类似<str这样的残缺片段。

浏览器在解析残缺HTML时会尽力容错,结果就是标签被当作纯文本显示出来,或者闭合标签丢失导致后续样式全部错乱。比如输出到一半时页面上突然出现一串ong>的文字,视觉效果非常糟糕。而且这种错误是随机的,取决于光标速度和标签位置的相对关系,调试起来很折磨人。

另一个容易被忽视的问题是事件绑定。如果打字内容里包含按钮或链接,并且这些元素上挂着事件,每次html()整体重写都会销毁并重建DOM节点,之前绑定的事件会全部失效。所以正确的思路应该是:不要重写整个容器,而是只追加增量内容,并且把HTML标签当作一个不可拆分的整体来处理。

二、核心思路:按节点拆分,标签整块输出

解决思路是把原始HTML字符串交给浏览器解析成一个临时的节点列表,然后遍历这个列表。遇到文本节点,就把文本再按字符拆开,一个一个追加;遇到元素节点,就把它整个克隆并一次性插入,插入后递归处理它的子节点。这样标签永远以完整形态出现在DOM中,文本部分则保持逐字的打字节奏。

不过递归处理子节点会让代码变复杂,这里介绍一种更轻量的等价方案:先把整段HTML解析成节点序列,再把每个文本节点拆成单字符,同时保留元素节点原样,最终得到一个扁平的输出队列。输出队列里的每一项,要么是一个字符,要么是一个完整的标签节点。之后只需要一个定时器从队列头部逐项取出并追加即可。

先看解析拆分部分的实现:

/**
 * 把包含HTML标签的字符串拆成输出队列
 * 文本拆成单字符,标签作为整体保留
 */
function buildQueue(html) {
  var queue = [];
  // 借助一个临时div解析HTML字符串
  var holder = $('<div>').html(html);
  var nodes = holder[0].childNodes;

  // 用队列遍历,顺便处理嵌套标签内部的文本
  var pending = Array.prototype.slice.call(nodes);
  while (pending.length) {
    var node = pending.shift();

    if (node.nodeType === 3) {
      // 文本节点:拆成单字符依次入队
      var text = node.nodeValue;
      for (var i = 0; i < text.length; i++) {
        queue.push(document.createTextNode(text.charAt(i)));
      }
    } else {
      // 元素节点:整块入队,子节点继续展开处理
      queue.push(node.cloneNode(false)); // 先入队标签本身(不含子节点)
      var children = Array.prototype.slice.call(node.childNodes);
      // 子节点插到当前标签之后,顺序用临时标记记录
      queue.push({ openTag: node, children: children });
    }
  }
  return queue;
}

上面的写法把开标签、子内容、闭标签的顺序问题留了一个尾巴,实际落地时更推荐用下面这个改良版:直接把每个元素节点连同内部文本一起处理,内部文本同样拆字符,闭标签用一个占位标记表示,输出到该标记时给对应的已打开元素追加后续字符即可。为了代码简洁,下面给出一个完整可运行的实现,它采用游标递归的方式,逻辑更直观:

(function ($) {
  $.fn.typewriter = function (options) {
    var settings = $.extend({
      speed: 80,        // 每个字符的间隔毫秒数
      cursor: '|'       // 光标字符
    }, options);

    this.each(function () {
      var $this = $(this);
      var html = $this.html();
      $this.empty();

      // 闪烁光标
      var $cursor = $('<span class="tw-cursor">')
        .text(settings.cursor)
        .appendTo($this);

      var index = 0;

      // 每次输出一个最小单元:一个字符或一个完整标签
      function step() {
        if (index >= html.length) {
          $cursor.trigger('done');
          return;
        }
        var ch = html.charAt(index);

        if (ch === '<') {
          // 遇到标签:找到结束尖括号,整块输出
          var end = html.indexOf('>', index);
          var tagStr = html.substring(index, end + 1);
          $(tagStr).insertBefore($cursor);
          index = end + 1;
        } else {
          document.createTextNode(ch)
            .insertBefore ? null : null;
          // 用原生方式插入纯文本字符,避免特殊字符被解析
          $cursor.before(document.createTextNode(ch));
          index++;
        }
        setTimeout(step, settings.speed);
      }

      step();
    });
    return this;
  };
})(jQuery);

这段代码的关键点有两个。第一,通过charAt(index)判断当前字符是不是<,是的话就用indexOf('>')找到标签结尾,把整段标签字符串交给jQuery解析并插入,标签就以完整形态进入DOM。第二,纯文本字符不用$cursor.before(ch)而是用before(document.createTextNode(ch)),这样即使文本里包含<&等特殊字符,也会被当作字面文本显示,不会被误当成标签解析。不过要注意,如果文本里本身就有<字符的转义形式&lt;,按字符截断时可能把&lt;拆成&lt;两部分,插入文本节点后显示会短暂异常,这种情况需要先对HTML实体做保护处理,下文会提到。

三、光标闪烁与CSS配合

光标是打字机效果的灵魂。上面代码里给光标加了tw-cursor类名,配合下面这段CSS就能实现闪烁:

.tw-cursor {
  display: inline-block;
  width: 2px;
  min-width: 2px;
  background: #333;
  color: transparent;
  margin-left: 2px;
  animation: blink 1s step-end infinite;
}

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

这里用step-end而不是默认的平滑过渡,因为真实光标的闪烁是瞬间切换的,平滑淡入淡出反而显得假。另外建议把光标宽度设为2px并用background画出来,字符形式的光标在不同字体下宽度不一,容易和文字重叠。打字结束后可以监听自定义事件done来移除光标,或者让光标继续闪烁停留几秒再淡出,看具体的视觉需求。

四、进阶玩法:速度变化、循环与多段依次输出

真实打字的速度不是恒定的,人类敲键盘有快有慢,标点后还会停顿。可以在step里加入随机延迟来增强真实感:

function nextDelay(ch, base) {
  // 标点后停顿更久
  if ('。,!?.!?'.indexOf(ch) >= 0) {
    return base * 6;
  }
  // 正常字符加一点随机抖动
  return base * (0.6 + Math.random() * 0.8);
}

循环播放也是常见需求,比如首页标语轮播。思路是打完后清空容器,切换到下一段内容重新开始。用Promise风格的写法把每段打字包装成可等待的任务,再用async或回调串联即可。多段依次输出(比如终端里逐行打印命令)则可以给插件加一个done回调,在一个元素完成后触发下一个元素的打字。

驱动方式上,setTimeoutrequestAnimationFrame都可行。前者写起来直观,延迟控制精确到毫秒;后者与浏览器渲染帧同步,页面切到后台时会自动暂停,节省资源,适合长时间运行的页面。如果打字速度设置得很快(比如20毫秒以下),建议改用requestAnimationFrame加时间累积的方式,避免定时器堆积导致卡顿。

五、常见坑与排查方法

第一个坑前面提过:HTML实体被拆断。解决方法是在构建输出队列前,先把&lt;&amp;这类实体当成不可拆分的单元处理,判断当前字符是&时向后查找到分号,整体作为一个文本单元输出。

第二个坑是自闭合标签和属性中包含>的情况。属性值里出现>(比如data-tip="a>b")会让简单的indexOf('>')提前截断。稳妥的做法是用DOMParser解析而不是字符串查找,或者约定打字内容里不在属性中写>。对绝大多数展示型文案来说,后者完全够用。

第三个坑是容器高度跳动。打字过程中内容不断变长,如果容器没有固定高度,页面下方元素会被推来推去。给容器设置min-height,或者在打字前先隐形渲染一遍完整内容量出高度再锁定,都能解决。

最后提醒一点,如果打字内容是由用户输入或接口返回的动态数据,务必做好转义或白名单过滤,只允许有限的标签通过,防止把打字机效果变成XSS注入的入口。展示层做得再炫,安全底线也不能丢。

jQuery打字机效果Typewriter效果HTML逐字输出修改时间:2026-09-12 21:12:47

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