算法与数据结构 / 动画引擎原理 · 从插值到播放控制 / 文本动画:逐字、逐词、打字机 待审核 13 / 47
split-text · stagger · typewriter

文本动画:逐字、逐词、打字机

一句话整体淡入只是一个动画。要让文字像波浪一样依次浮现,得先把它拆开:split-text 用 JS 把文本切成字、词或行,每个单位包进独立的 <span>,再给它们一段相同的入场动画,只在起始延迟上拉开差距——那正是 stagger 做的事。

打字机是另一条路。它不拆 DOM,也不给谁加延迟,而是让「已显现的字符数」这一个整数随时间增长。两种做法在实现上几乎没有共同点。

1 · 拆分粒度与波的方向

粒度决定拆成什么单位,from 决定波从哪里发起。每个单位是一个独立的 Animation,延迟由 stagger 按它到发起点的距离算出。

图 1-1 · 同一句话按字、词、行三种粒度拆分后的交错入场。可切换粒度与方向,调 each 观察末单位延迟随之变化。

警示 · 包裹用的 <span> 必须 display: inline-block,否则 translateY 对纯 inline 元素不生效——整段会「淡入但不上浮」,且没有任何报错。按字拆时这一条尤其容易漏。按词拆时空格要单独保留成文本节点(或给单位加 white-space: pre),否则相邻两个 inline-block 之间的空白会被折叠,词粘在一起。

拆分粒度的取舍很直接:按字最细、波最连贯,但会切开单词与连字,英文长句慎用;按词保留单词完整;按行最稳,适合大段排版。

2 · 打字机

把「显现到第几个字」建模成一个 keyframes: [0, total]ease: 'linear' 的动画,onUpdateMath.floor 取整后 slice 写回。速度参数是字每秒,换算成每字耗时后乘字数即得总时长。光标是一个常驻元素,闪烁交给 CSS 的 steps(1)

图 2-1 · 逐字 reveal 与闪烁光标。可调速度,读数给出换算后的每字耗时与当前进度。

注 ·ease: 'linear' 不是随手选的默认值。被补间的「值」是字符下标,而 onUpdate 里的 Math.floor 把连续值量化成整数——非线性缓动会让相邻两字的间隔忽长忽短,听感上像敲字的人时快时慢。真要模拟人手,该调的是每字耗时的随机抖动,而不是整段的缓动曲线。

3 · 两种 reveal 的分野

split-text 是 NN 个动画加 NN 个延迟,所有单位都参与运动,只是先后错开;typewriter 是 1 个动画加一个递增的整数游标,运动的不是字符而是「显现到第几个」。

代价也各在一边。split-text 要真的改 DOM 结构,字数一多就是几百个 <span> 与几百个 Animation;typewriter 只动一个文本节点,但每帧重设 textContent 会让整行重新布局,长文本同样不便宜。

import { Animation, stagger } from '@vega/anim';

// split-text: 拆成单位, 每个包进 inline-block 的 span, 再配一个递增 delay
const delayFor = stagger(55, { from: 'first', ease: 'easeOut' });
units.forEach((el, i) => {
  new Animation({
    keyframes: [0, 1], duration: 420, ease: 'easeOut',
    delay: delayFor(i, units.length),  // 第 i 个晚一点起
    onUpdate: (p) => {
      el.style.opacity = p;
      el.style.transform = `translateY(${(1 - p) * 8}px)`;
    },
  });
});

// typewriter: 显现字符数 0 → total 线性增长, 截取 slice 写回
new Animation({
  keyframes: [0, text.length],
  duration: (1000 / cps) * text.length,
  ease: 'linear',                      // 匀速敲字
  onUpdate: (value) => { host.textContent = text.slice(0, Math.floor(value)); },
});

入场那条「上浮」轨迹的编排细节见关键帧与编排;每个单位的透明度与位移如何逐帧算出见插值。让字形本身在两个字之间连续变形,则是另一套做法,见文字形变