G3:GSAP 文本动画——SplitText、ScrambleText 与逐字魔法

GSAP 动画实战系列 系列总览 | G1 Tween & Timeline | G2 ScrollTrigger | G3 文本动画 ← 本文 | G4 SVG 动画 | G5 GSAP + React | G6 AI 项目实战 | G7 社区案例


# GSAP 文本动画 ## SplitText - 拆分类型:chars words lines - 逐字淡入 / 翻转 / 弹跳 - 与 ScrollTrigger 结合 ## ScrambleText - 乱码效果配置 - 字符集自定义 - LLM 流式输出动画 ## TextPlugin - 文本替换动画 - 打字机效果 - 数字递增动画 ## 生产案例 - Hero 标题动画 - 计数器滚动联动 - AI 对话逐字输出

GSAP 提供了三个文本相关的插件,按功能递进:

插件 功能 许可
TextPlugin 替换文本内容(打字机效果、数字递增) 免费
ScrambleText 乱码随机字符效果 免费
SplitText 把文本拆成 chars/words/lines 免费(原付费,Webflow 收购后免费)

1. SplitText——文本的乐高积木

🎬 SplitText 逐字动画效果预览CodePen 演示 | ScrambleText 乱码效果CodePen 演示

SplitText 把一个文本元素拆分为独立的 <div>,每个字符/词/行都是独立可动画的 DOM 元素。

1.1 基础拆分

<h1 class="hero-title">你好,世界</h1>
import SplitText from 'gsap/SplitText'
gsap.registerPlugin(SplitText)

const split = new SplitText('.hero-title', {
  type: 'chars,words,lines',  // 拆成字符、单词、行
  charsClass: 'char',          // 每个字符的 class
  wordsClass: 'word',
  linesClass: 'line',
})

结果 DOM(简化):

<h1 class="hero-title">
  <div class="line" style="display: block; text-align: center;">
    <div class="word" style="display: inline-block;">
      <div class="char" style="display: inline-block;">你</div>
      <div class="char" style="display: inline-block;">好</div>
    </div>
    <div class="word" style="display: inline-block;">
      <div class="char" style="display: inline-block;">世</div>
      <div class="char" style="display: inline-block;">界</div>
    </div>
  </div>
</h1>

现在每个 .char 都可以独立动画了。

1.2 经典效果:逐字淡入

const split = new SplitText('.title', { type: 'chars' })
const chars = split.chars  // 所有字符元素的数组

gsap.from(chars, {
  opacity: 0,
  y: 40,
  duration: 0.4,
  stagger: 0.03,       // 每个字符间隔 0.03s
  ease: 'power3.out',
})

效果:标题从下方逐个字符弹入。

1.3 逐字翻转

gsap.from(chars, {
  rotationX: -90,      // 绕 X 轴翻转
  opacity: 0,
  duration: 0.5,
  stagger: 0.05,
  transformOrigin: '50% 0%',  // 翻转轴心在顶部
  ease: 'back.out(1.7)',
})

1.4 SplitText + ScrollTrigger

滚动到标题区域时逐字浮现:

const split = new SplitText('.section-title', { type: 'chars' })

gsap.from(split.chars, {
  opacity: 0,
  y: 50,
  duration: 0.6,
  stagger: 0.02,
  ease: 'power3.out',
  scrollTrigger: {
    trigger: '.section-title',
    start: 'top 80%',
    // toggleActions: 'play none none reverse',
  }
})

1.5 清理 revert()

SplitText 会修改 DOM,在 SPA 中组件卸载时必须还原:

useEffect(() => {
  const split = new SplitText('.title', { type: 'chars' })
  gsap.from(split.chars, { ... })
  
  return () => split.revert()   // 还原 DOM
}, [])

2. ScrambleText——黑客帝国风格

ScrambleText 让文本以随机字符乱码的形式出现,最终稳定到目标文字。非常适合数字、代码、或者科幻风格的展示。

2.1 基础乱码

<div class="scramble">1000000</div>
import { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin'
gsap.registerPlugin(ScrambleTextPlugin)

gsap.to('.scramble', {
  scrambleText: {
    text: '2800000',      // 最终显示的文本
    chars: '0123456789',  // 随机字符集(只在这组里随机)
    revealDelay: 0.5,     // 每个字符延迟
    speed: 0.3,           // 乱码变化速度
    tweenLength: false,   // false = 到文本匹配时才停
  },
  duration: 2,
})

2.2 自定义字符集

gsap.to('.code-text', {
  scrambleText: {
    text: 'const result = await fetch("/api/data")',
    chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_/{}()',
    revealDelay: 0.8,
    speed: 0.2,
  },
  duration: 3,
})

2.3 与新的 AI 场景结合

ScrambleText 有一种天然的"科技感"。在 AI 项目中,它恰好可以用来模拟:

LLM 流式输出动画

function animateAIResponse(element, finalText) {
  gsap.to(element, {
    scrambleText: {
      text: finalText,
      chars: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
      revealDelay: 0.3,
      speed: 0.15,
      newClass: 'revealed-char',  // 已确定的字符添加这个 class
    },
    duration: Math.max(1, finalText.length * 0.05),  // 按文本长度计算
  })
}

模型思考/loading 状态

// 持续变化的"思考中"文本
gsap.to('.ai-thinking', {
  scrambleText: {
    text: '{thinking}',  // 目标文本
    chars: '▮▯▰▱▲△▼▽◀▶◆◇○◎●◐◑◒◓◔◕',
    speed: 0.1,
    tweenLength: true,  // animation duration 结束后就停
  },
  duration: 0.5,
  repeat: -1,
  yoyo: true,
})

2.4 与 SplitText 组合

先把文本拆成字符,再逐个 scramble:

const split = new SplitText('.title', { type: 'chars' })

gsap.fromTo(split.chars, 
  { opacity: 0 },
  {
    opacity: 1,
    duration: 0.1,
    stagger: {
      each: 0.05,
      onStart: function() {
        // 每个字符开始时用 scramble 效果
        gsap.to(this.targets()[0], {
          scrambleText: {
            text: this.targets()[0].textContent,
            chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
            speed: 0.3,
          },
          duration: 0.2,
        })
      }
    }
  }
)

3. TextPlugin——打字机效果

TextPlugin 直接替换元素的 textContent,逐字显示。

import { TextPlugin } from 'gsap/TextPlugin'
gsap.registerPlugin(TextPlugin)

gsap.to('.typewriter', {
  text: '你好,这是打字机效果的文本。',  // 注意属性名是 text
  duration: 2,
  ease: 'none',      // 匀速逐字出现
  delay: 0.5,
})

3.1 数字递增(计数器)

gsap.to('.counter', {
  text: {
    value: '98765',            // 目标文本
    speed: 15,                 // 跳变速度
    snap: { text: 1 },         // 整数跳变
  },
  duration: 2,
  ease: 'power2.out',
})

配合 ScrollTrigger 实现滚动到某个位置时数字飙升——投资回报、用户数、统计数据展示的经典模式。

3.2 打字机 + 光标闪烁

const tl = gsap.timeline()

tl.to('.typewriter', {
  text: 'hello world',
  duration: 1.5,
  ease: 'none',
})
.to('.cursor', {
  opacity: 0,
  duration: 0.5,
  repeat: -1,
  yoyo: true,
  ease: 'steps(1)',  // 阶梯闪烁
})

4. 生产级案例

案例:AI 聊天逐字输出

这是 AI 项目中最常见的需求——模拟 LLM 流式响应的逐字显示:

class AIStreamAnimator {
  constructor(element) {
    this.element = element
    this.displayText = ''
  }
  
  appendWord(word) {
    this.displayText += word
    // 平滑更新——不重新动画,而是跳过已显示的部分
    gsap.to(this.element, {
      text: this.displayText,
      duration: word.length * 0.03,
      ease: 'none',
    })
  }
  
  appendBulk(text) {
    const target = this.displayText + text
    gsap.to(this.element, {
      text: target,
      duration: Math.min(text.length * 0.02, 2),
      ease: 'none',
      onComplete: () => { this.displayText = target }
    })
  }
}

案例:数字对比动画

展示"从 X 增长到 Y"的统计对比,在数据可视化仪表盘中使用:

function animateStatChange(element, oldValue, newValue) {
  const tl = gsap.timeline()
  
  // 第一步:数字下降(模拟变化)
  tl.to(element, {
    text: { value: oldValue, speed: 20 },
    duration: 0.4,
    ease: 'power2.in',
  })
  // 第二步:飙升到新值
  .to(element, {
    text: { value: newValue, speed: 30 },
    duration: 1,
    ease: 'power2.out',
  }, '+=0.2')
}

5. 踩坑记录

  1. SplitText 拆分后文字可能换行不均——设置 wordDelimiter: ' '(英文),保证单词不拆分。

  2. 移动端 SplitText 性能——拆分大量文本(>500 字符)时,移动端可能会卡。解决方案:只拆分可见区域内的文本,用 IntersectionObserver 懒拆分。

  3. ScrambleText 的 tweenLength 容易误解——tweenLength: false 意味着每个字符匹配才停,可能导致动画比 duration 长。如果不希望超出,设 tweenLength: true

  4. SPA 中 SplitText 必须 revert()——React/Vue 组件卸载时,SplitText 改的 DOM 如果不还原,下次渲染会叠加嵌套。


下一篇:G4 SVG 动画——DrawSVG、MorphSVG 与 MotionPath

实操清单

  • 安装并注册 SplitText 插件:import SplitText from 'gsap/SplitText',调用 gsap.registerPlugin(SplitText)
  • new SplitText('.hero-title', { type: 'chars,words,lines' }) 拆分标题,打开 DevTools 确认 DOM 中已生成独立的 .char 元素
  • gsap.from(split.chars, { opacity: 0, y: 40, stagger: 0.03 }) 实现逐字淡入效果,调整 stagger 值观察节奏变化
  • rotationX: -90transformOrigin: '50% 0%' 加入逐字动画,实现翻转入场效果
  • 将 SplitText 动画与 ScrollTrigger 结合,配置 triggerstart: 'top 80%',滚动到标题时触发逐字浮现
  • 在 React useEffect 中使用 SplitText,并在清理函数里调用 split.revert() 还原 DOM,防止组件重渲染时 DOM 叠加
  • 注册 ScrambleTextPlugin,对一个数字元素执行乱码动画,仅使用 chars: '0123456789' 字符集,观察数字从旧值"乱码"到新值的过渡
  • 修改 ScrambleText 的 speedrevealDelay 参数,感受两者对乱码节奏的不同影响;再尝试切换 tweenLength: true/false 观察动画时长差异
  • 注册 TextPlugin,用 gsap.to('.typewriter', { text: '...', duration: 2, ease: 'none' }) 实现匀速打字机效果
  • 在打字机动画完成后,对光标元素添加 repeat: -1, yoyo: true, ease: 'steps(1)' 实现闪烁效果
  • 用 TextPlugin 的数字递增模式(text: { value: '98765', snap: { text: 1 } })结合 ScrollTrigger,实现滚动触发计数器飙升
  • 封装 AIStreamAnimator 类,调用 appendWord()appendBulk() 方法模拟 LLM 流式输出,验证文本平滑追加而不重置动画