G3:GSAP 文本动画——SplitText、ScrambleText 与逐字魔法
GSAP 动画实战系列 系列总览 | G1 Tween & Timeline | G2 ScrollTrigger | G3 文本动画 ← 本文 | G4 SVG 动画 | G5 GSAP + React | G6 AI 项目实战 | G7 社区案例
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. 踩坑记录
-
SplitText 拆分后文字可能换行不均——设置
wordDelimiter: ' '(英文),保证单词不拆分。 -
移动端 SplitText 性能——拆分大量文本(>500 字符)时,移动端可能会卡。解决方案:只拆分可见区域内的文本,用 IntersectionObserver 懒拆分。
-
ScrambleText 的
tweenLength容易误解——tweenLength: false意味着每个字符匹配才停,可能导致动画比 duration 长。如果不希望超出,设tweenLength: true。 -
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: -90和transformOrigin: '50% 0%'加入逐字动画,实现翻转入场效果 - 将 SplitText 动画与 ScrollTrigger 结合,配置
trigger和start: 'top 80%',滚动到标题时触发逐字浮现 - 在 React
useEffect中使用 SplitText,并在清理函数里调用split.revert()还原 DOM,防止组件重渲染时 DOM 叠加 - 注册 ScrambleTextPlugin,对一个数字元素执行乱码动画,仅使用
chars: '0123456789'字符集,观察数字从旧值"乱码"到新值的过渡 - 修改 ScrambleText 的
speed和revealDelay参数,感受两者对乱码节奏的不同影响;再尝试切换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 流式输出,验证文本平滑追加而不重置动画