从补间到叙事:GSAP 动画实战 · 公开阅读
第18章 GSAP高级插件概览
了解 SplitText、ScrambleText、DrawSVG 和 MorphSVG 等高级插件。
本章小节
18.1 GSAP 3.13 起的免费插件体系
从 GSAP 3.13(2025年4月发布)起,核心与全部插件都免费,包括商业使用。过去仅向 Club GSAP 会员提供的 SplitText、MorphSVG 等插件已纳入公开的 npm 包,不再需要会员订阅或私有仓库令牌。参见 官方发布说明。
免费使用不等于 MIT 许可;GSAP 使用自己的标准许可,具体以官方许可为准。
| 组成部分 | 是否免费 | 说明 |
|---|---|---|
gsap 核心 | 免费 | to/from/fromTo/timeline 等核心API |
ScrollTrigger | 免费 | 滚动驱动动画 |
Observer | 免费 | 统一手势处理 |
MotionPathPlugin | 免费 | 路径运动动画 |
Flip | 免费 | 布局过渡动画 |
ScrollToPlugin | 免费 | 平滑滚动到指定位置 |
EaselPlugin | 免费 | EaselJS/CreateJS集成 |
PixiPlugin | 免费 | PixiJS集成 |
TextPlugin | 免费 | 文本替换动画 |
| SplitText | 免费 | 将文本拆分为行、词、字符,支持逐字动画 |
| ScrambleText | 免费 | 文字乱码解码动画效果 |
| DrawSVGPlugin | 免费 | SVG线条逐步描绘动画 |
| MorphSVGPlugin | 免费 | SVG形状平滑变形动画 |
| Physics2DPlugin | 免费 | 2D物理模拟,支持重力、摩擦力等 |
| PhysicsPropsPlugin | 免费 | 物理属性模拟 |
| CustomEase | 免费 | 自定义缓动曲线 |
| GSDevTools | 免费 | 开发调试工具,时间轴可视化控制 |
| InertiaPlugin | 免费 | 拖拽松手后带惯性滑行 |
18.2 公开安装与按需导入
旧的 Simply GreenSock、Shockingly GreenSock、Business GreenSock 会员等级属于历史信息,不再作为当前安装或部署的前提。本书示例统一使用公开的 GSAP 3.13 包:
npm install gsap@3.13.0import gsap from "gsap";
import { SplitText } from "gsap/SplitText";
import { MorphSVGPlugin } from "gsap/MorphSVGPlugin";
gsap.registerPlugin(SplitText, MorphSVGPlugin);本章各示例假设核心与对应插件已加载并注册。只导入实际使用的插件,不需要加载整套工具。通过 <script> 引入时,应先加载核心,再加载同版本插件;路径均来自 gsap 包的 dist 目录。详见 官方安装文档。
18.3 SplitText
SplitText 将一段文字拆分成**行(lines)、词(words)、字符(chars)**三个层级,让每个文字碎片独立动画。
核心代码
<h1 class="split-title">从补间到叙事</h1>
<script>
const st = new SplitText(".split-title", {
type: "lines,words,chars", // 拆分方式
linesClass: "line", // 行容器的class
wordsClass: "word", // 词容器的class
charsClass: "char", // 字符容器的class
});
// 字符逐个飞入
gsap.from(st.chars, {
opacity: 0,
y: 60,
rotation: 10,
duration: 0.6,
stagger: 0.03,
ease: "back.out(1.7)",
});
// 清理(组件卸载时)
st.revert();
</script>三种经典用法
const st = new SplitText(".title", { type: "chars,words" });
// 用法1:入场(in)—— 文字从不可见飞进来
gsap.from(st.chars, {
opacity: 0,
y: 80,
rotationX: -90,
stagger: 0.04,
duration: 0.8,
ease: "power4.out",
});
// 用法2:退场(out)—— 文字逐个消失
gsap.to(st.chars, {
opacity: 0,
y: -50,
stagger: { from: "end", amount: 0.3 },
duration: 0.4,
});
// 用法3:效果(effect)—— 不断变化的视觉
gsap.to(st.chars, {
color: "#e74c3c",
fontWeight: 700,
stagger: { from: "center", amount: 0.5 },
duration: 0.3,
yoyo: true,
repeat: 1,
});逐行动画
const st = new SplitText(".paragraph", { type: "lines" });
gsap.from(st.lines, {
opacity: 0,
x: 30,
duration: 0.5,
stagger: 0.1,
scrollTrigger: {
trigger: ".paragraph",
start: "top 80%",
},
});把这一节跑一遍:SplitText 字符入场
真实插件按 Unicode 文本拆分。先保留可访问的原文名称,再对子元素做有限时长 Tween;清理时恢复原文。逐行场景与逐字场景的测量时机不同。
实验:SplitText 字符入场
GSAP 3.13 / 实验亲手调整参数,观察动画差异。点击开启后再播放,不会自动启动动画。
观察与验证:观察中英混排与 emoji,重置后确认没有越来越多的包装层。需要可复制或被辅助技术读取的正文,避免无意义的拆字。
18.4 ScrambleText
文字从随机乱码字符逐渐解码为正确内容的动画效果。适合科技感、黑客风、解谜类场景。
核心代码
<h1 id="decrypt">解码中...</h1>
<script>
const tl = gsap.timeline();
tl.to("#decrypt", {
scrambleText: {
text: "从补间到叙事",
chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", // 乱码字符集
revealDelay: 0.5, // 开始揭示前的整体延迟(秒)
speed: 0.3, // 乱码更新速度系数,不是秒
newClass: "revealed", // 揭示后的字符添加的class
tweenLength: false, // 是否动画文本长度
},
duration: 2,
});
</script>参数详解
scrambleText: {
text: "目标文字", // 必需:最终显示的文字
chars: "!@#$%^&*()_+-=[]{}|;:", // 乱码阶段使用的随机字符集
revealDelay: 0, // 开始揭示前的整体延迟
speed: 0.3, // 乱码字符滚动的速度
newClass: "fresh", // 揭示后添加到span上的class
oldClass: "scrambled",// 乱码中的字符的class
tweenLength: false, // true时会动画文字长度变化
rightToLeft: false, // 从右向左揭示
delimiter: "", // 分隔符
}把这一节跑一遍:ScrambleText 文字解码
revealDelay 是开始揭示前的整体延迟;speed 是乱码更新系数,不是秒。tweenLength 影响长度变化,不是字间距。中间字符随机,不能要求每次重播完全一致。
实验:ScrambleText 文字解码
GSAP 3.13 / 实验亲手调整参数,观察动画差异。点击开启后再播放,不会自动启动动画。
观察与验证:暂停并定位不同进度,检查最终文字与参数。核心信息不要只依赖解码动画才能获得;这里原文也出现在正文。
18.5 DrawSVGPlugin
让 SVG 线条从 0% 到 100% 逐渐描出。适合签名动画、Logo 描边效果。
核心代码
<svg viewBox="0 0 800 200">
<path id="signature"
d="M50,100 C150,30 250,170 350,100 S550,30 650,100 S700,150 750,80"
fill="none" stroke="#3498db" stroke-width="4"/>
</svg>
<script>
gsap.registerPlugin(DrawSVGPlugin);
gsap.from("#signature", {
drawSVG: 0, // 从0%开始描绘
duration: 3,
ease: "power2.inOut",
});
// 或直接使用 to
gsap.to("#signature", {
drawSVG: "100%", // 描绘到100%
duration: 3,
ease: "power2.inOut",
});
</script>高级:分段描绘——不是从头画到尾
// 从20%描绘到80%
gsap.fromTo("#path",
{ drawSVG: "20% 20%" }, // 起点20%,终点也在20%(线条不可见)
{ drawSVG: "20% 80%", duration: 2 } // 起点20%,终点80%
);
// 描绘 + 擦除效果
const tl = gsap.timeline({ repeat: -1, yoyo: true });
tl.fromTo("#path",
{ drawSVG: 0 },
{ drawSVG: "100%", duration: 2, ease: "power2.inOut" }
);
tl.to("#path", { drawSVG: "0% 100%", duration: 1, ease: "power2.in" });把这一节跑一遍:DrawSVG 描绘范围
DrawSVG 改变 stroke 的可见范围,不改变路径几何。20% 20% 为零长度,20% 80% 为中间 60% 的路径;百分比参考路径总长而非 SVG 宽度。
实验:DrawSVG 描绘范围
GSAP 3.13 / 实验亲手调整参数,观察动画差异。点击开启后再播放,不会自动启动动画。
观察与验证:切换整条与局部范围,查看可见段长度。对比第17章:DrawSVG 是线的呈现,MotionPath 是物体的位置。
18.6 MorphSVGPlugin
将 SVG 路径从形状 A 平滑变形为形状 B。
核心代码
<svg viewBox="0 0 300 300">
<path id="shape" d="M150,50 L250,150 L150,250 L50,150 Z" fill="#3498db"/>
</svg>
<button onclick="morph()">变换形状</button>
<script>
gsap.registerPlugin(MorphSVGPlugin);
function morph() {
// 菱形 → 圆形
gsap.to("#shape", {
duration: 1.5,
morphSVG: "M150,20 C230,20 280,70 280,150 C280,230 230,280 150,280 C70,280 20,230 20,150 C20,70 70,20 150,20 Z",
ease: "power2.inOut",
});
}
</script>在多个形状之间来回切换
// 在多个路径之间变形
const shapes = [
"#star", // 星形
"#hexagon", // 六边形
"#circle", // 圆形
"#square", // 方形
];
let index = 0;
document.querySelector("button").addEventListener("click", () => {
index = (index + 1) % shapes.length;
gsap.to("#shape", {
duration: 1,
morphSVG: shapes[index],
ease: "power2.inOut",
});
});
// findShapeIndex 辅助
// shapeIndex:"auto" 让插件自动匹配;视觉辅助使用独立的 findShapeIndex 工具,非 MorphSVGPlugin 静态方法。关键参数
morphSVG: {
shape: "#targetShape", // 目标形状的选择器或路径字符串
shapeIndex: 2, // 调整两条路径的起始点匹配
type: "linear", // 默认 linear;可选 rotational
map: "size", // 控制点映射方式:"size" | "position" | "complexity"
origin: "50% 50%", // 变形中心点
precompile: "log", // 输出可预编译的数据,或传入预编译路径数组
}把这一节跑一遍:MorphSVG 路径变形
MorphSVG 处理路径匹配与插值。shapeIndex 调整两条路径起始点的匹配,不是目标数组的索引。两端形状的点数可不同,但匹配方式影响变形中途是否扭曲。
实验:MorphSVG 路径变形
GSAP 3.13 / 实验亲手调整参数,观察动画差异。点击开启后再播放,不会自动启动动画。
观察与验证:停在 50% 观察中间轮廓,再回到两端。需要设计更复杂形状时先检查路径方向、闭合和起始点匹配。
18.7 Physics2DPlugin
让元素像真实物体一样受重力、摩擦力、加速度影响。设置初始速度和角度即可模拟抛物线运动。
核心代码
<div class="physics-ball">⚽</div>
<script>
gsap.registerPlugin(Physics2DPlugin);
gsap.to(".physics-ball", {
duration: 2,
physics2D: {
velocity: 300, // 初始速度(扔出去的力度)
angle: -45, // 发射角度(度)
gravity: 400, // 重力加速度(px/s²)
acceleration: 0, // 自身加速度
accelerationAngle: 0, // 加速度方向
friction: 0.2, // 摩擦系数(0-1,越大停得越快)
},
ease: "none", // 物理动画必须用none
});
</script>弹跳效果
Physics2D 计算带速度、重力和摩擦的轨迹,但不提供碰撞检测,也不支持 physics2D.onUpdate(velocity,angle) 返回新的速度来反弹。不要照抄不存在的接口。
如果需要碰撞,应在独立的物理更新循环中保存位置与速度,做边界判断后反转相应速度分量,或使用物理引擎。只需表现一次落地回弹时,可用 Timeline 编排下降与缩放/回弹,但那是设计的视觉效果,不是 Physics2D 的碰撞模拟。
// 有限时长抛物线,超出容器可以被裁剪;它不会自动反弹。
gsap.to(ball, { duration:2, ease:'none', physics2D:{ velocity:200, angle:-60, gravity:300 } });把这一节跑一遍:Physics2D 抛物线
每个粒子使用初速度、角度和重力。无摩擦时 y 随时间包含二次项,速度持续变化;ease:none 保持物理时间映射。实验根据预测的轨迹范围等比例缩放世界坐标到舞台,粒子的显示大小保持不变。这个视图变换不会改变物理速度、重力或时间;裁剪和视图缩放都不是碰撞。
实验:Physics2D 抛物线
GSAP 3.13 / 实验亲手调整参数,观察动画差异。点击开启后再播放,不会自动启动动画。
观察与验证:把重力改为 0 看直线运动,再增大重力看抛物线。暂停读取 t,思考 y≈vy×t+0.5×g×t²;实际插件负责求值。
18.8 CustomEase
自定义缓动曲线,通过贝塞尔路径精确控制加速/减速/弹跳/回弹节奏。
核心代码
gsap.registerPlugin(CustomEase);
// 方法1:可视化创建
CustomEase.create("myBounce", "M0,0 C0.5,0 0.5,1 1,1");
// 路径格式:M(startX,startY) C(cp1X,cp1Y, cp2X,cp2Y, endX,endY)
// 方法2:从GreenSock可视化工具复制
// 访问 https://gsap.com/docs/v3/Eases/CustomEase/
CustomEase.create("elasticCustom", "M0,0,C0.2,0,0.2,0.8,0.5,1,0.8,0.8,0.8,0,1,0");
// 使用
gsap.to(".box", {
x: 300,
duration: 2,
ease: "myBounce",
});使用GreenSock可视化缓动编辑器
访问 GreenSock Ease Visualizer:在线拖拽曲线控制点,画好了复制生成的字符串就行。
// 强烈弹性——超出目标很多再弹回来
CustomEase.create("superElastic", "M0,0 C0.2,0.1 0.25,1 0.25,1 C0.25,1 0.3,0.3 0.3,0.3 C0.3,0.3 0.35,1 0.35,1 C0.4,0.4 0.45,0.1 0.5,0 C0.6,-0.1 0.7,0.2 0.8,0.5 C0.9,0.8 0.95,0.6 1,1");
gsap.from(".item", {
opacity: 0,
y: 100,
duration: 1.5,
stagger: 0.1,
ease: "superElastic",
});把这一节跑一遍:自定义贝塞尔缓动
这个实验复用第8章真实 CustomEase 模块。缓动是 p 到 q 的函数,可独立用于对象、DOM 和 WebGL;导入注册的责任属于应用代码,不属于自由执行的 MDX 字符串。
实验:自定义贝塞尔缓动
GSAP 3.13 / 实验亲手调整参数,观察动画差异。点击开启后再播放,不会自动启动动画。
观察与验证:用同一曲线驱动不同属性,先确保曲线端点与时间单调,再考虑超调是否适合 opacity 等有物理边界的属性。
18.9 GSDevTools
在页面底部添加时间轴控制器,支持拖拽、暂停、快进、慢放动画,调试时不需反复刷新浏览器。
// 仅在开发环境加载
if (process.env.NODE_ENV === "development") {
GSDevTools.create();
}
// 绑定到特定动画
const tl = gsap.timeline();
tl.to(".box", { x: 200, duration: 1 });
tl.to(".box", { y: 200, duration: 1 });
GSDevTools.create({ animation: tl });18.10 InertiaPlugin
InertiaPlugin 根据属性初速度和阻力计算自然减速。属性名是第一层:x 下放 velocity/min/max/end;resistance 和 duration 放在 inertia 配置中。
gsap.registerPlugin(InertiaPlugin);
gsap.to(slider, {
inertia: {
x: { velocity:200, min:0, max:500, end:gsap.utils.snap(20) },
resistance:500,
duration:{ min:0.5, max:3, overshoot:0 }
}
});end 可接受吸附函数;这里把预计终点吸附到 20px 网格,不存在 endSnap 参数。使用 velocity:'auto' 前先 InertiaPlugin.track(slider,'x'),在释放时创建惯性 Tween,并在卸载时 untrack。Observer 的实时输入速度与手工指定初速度要区分。
演示使用指定速度,便于重复验证边界;实际拖拽结合 Draggable/Observer 获取输入,并只在释放时启动惯性。
把这一节跑一遍:Inertia 速度与边界
初速度给 x,阻力设置在 inertia 层,时长由插件按规则决定。此处使用显式速度便于重复实验;真实拖拽可以先 track 获取自动速度,释放后再启动 Tween。
实验:Inertia 速度与边界
GSAP 3.13 / 实验亲手调整参数,观察动画差异。点击开启后再播放,不会自动启动动画。
观察与验证:设正负速度比较方向;从中间开始,终点应被限制在 0 到容器最大位移之间。不要在 drag 过程中同时让另一个 Tween 写 x。
18.11 常见陷阱
陷阱1:继续使用旧会员包或混用插件版本
# 核心与全部插件均来自公开的 gsap 包
npm install gsap@3.13.0旧项目应按 官方安装说明迁移到公开包。CDN 示例也要保持核心与插件版本一致,例如 gsap@3.13.0/dist/gsap.min.js 与 gsap@3.13.0/dist/SplitText.min.js。SplitText 3.13 已移除旧的 position: "absolute" 选项,不应继续照抄旧版本参数。
陷阱2:SplitText中文需要特殊处理
// 使用真实文本;仅拆字符时改善中英混排中的不自然断词
const st = new SplitText(".chinese-title", {
type: "chars",
smartWrap: true,
});SplitText 3.13 改善了 Unicode、组合 emoji 和多语言处理,不能简单描述为 charAt 遍历。应检查实际字体、标点和换行结果,并保留原文本需要的空格;涉及拆行时,结合 autoSplit 与 onSplit 处理字体加载和宽度变化。参见 SplitText 官方文档。
陷阱3:MorphSVG路径点数不一致
两个形状的控制点数量差距过大时,变形动画会看起来很诡异。解决方案:在矢量工具中统一路径点数,或用 shapeIndex:"auto" 和开发期可视化辅助检查匹配。
陷阱4:DrawSVG的百分比参考
drawSVG 的百分比是基于路径总长度,不是 viewBox。如果路径有多个子路径,行为可能不符合预期——合并为单个路径即可。
陷阱5:把Physics2D位置误认为由ease驱动
// 注意:物理位置不会变成 power2.out 曲线
gsap.to(".ball", {
physics2D: { velocity: 300, angle: -45, gravity: 400 },
ease: "power2.out", // Physics2D 的位置按时间计算;该缓动仍可影响同一 Tween 的其他属性
});
// 只做物理位置时,建议明确 ease: "none"
gsap.to(".ball", {
physics2D: { velocity: 300, angle: -45, gravity: 400 },
ease: "none",
});Physics2D 使用 Tween 的时间计算位置,不使用缓动后的比例。写 ease:"none" 有助于明确物理时间映射;如果同一个 Tween 还包含 opacity 等属性,它们仍会受到 ease 影响,必要时拆成独立 Tween。