关于 gsap-web
gsap-web 是面向网页开发的 GSAP 动画技能,提供时间轴编排、滚动动画、文字揭示和布局过渡的实现与调试指导,也涵盖独立 HTML 动画的验证流程。
主要功能
- 01GSAP 时间轴与精确编排
- 02ScrollTrigger 滚动动画
- 03SplitText 文字揭示
- 04Flip 布局过渡
- 05动画清理与性能建议
- 06独立 HTML 截图验证流程
适用场景
- 01制作滚动驱动的网页叙事
- 02编排多元素首屏动画
- 03实现文字分行揭示和布局切换
- 04调试平滑滚动与 ScrollTrigger 同步
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ iart-ai/ web-animation-skills --skill gsap-web安装后在 Claude Code 中输入/gsap-web确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdexamples/hero-timeline.jsstandalone-template.htmlreferences/scrolltrigger-cookbook.mdscrolltrigger-lenis.md详细介绍
面向 Web 的 GSAP
GSAP(GreenSock Animation Platform)是代码驱动 Web 动效的主力工具:可创建序列时间线、滚动驱动的叙事效果、文本显现效果和布局过渡效果。从 GSAP 3.12+ 起,所有插件(ScrollTrigger、SplitText、Flip、MotionPath、MorphSVG、Draggable、Observer)均可 100% 免费使用,包括商业用途。
适用场景
- 滚动驱动的叙事效果:固定区段、视差、进度拖动、水平滚动
- 具有精确重叠控制的序列式首屏动画和复杂多元素时间线
- 将文本拆分为字符、单词或行后逐一显现(SplitText)
- 元素位置、大小或父级发生变化时的布局过渡(Flip)
- 任何需要精细时间控制、精准缓动或命令式编排的动效
安装和注册
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
import { Flip } from "gsap/Flip";
gsap.registerPlugin(ScrollTrigger, SplitText, Flip);
使用前必须注册插件,否则插件会静默失效。使用打包工具时,请在应用入口处注册一次。
核心技巧
优先使用时间线
相比多个互不相关的补间动画,优先使用一个时间线——它提供统一的播放头、相对定位和便捷的反向播放。
const tl = gsap.timeline({ defaults: { ease: "power3.out", duration: 0.6 } });
tl.from(".title", { yPercent: 100, opacity: 0 })
.from(".sub", { y: 20, opacity: 0 }, "-=0.3") // start 0.3s before prev ends
.from(".cta", { scale: 0.9, opacity: 0 }, "<"); // align to prev tween START
位置参数速查:
"+=0.5"/"-=0.3"—— 相对于时间线末尾的位置(间隔 / 重叠)"<"—— 上一个补间动画的起点;">"—— 上一个补间动画的终点"<0.2"—— 上一个补间动画开始后 0.2 秒"myLabel"—— 位于通过tl.addLabel("myLabel")添加的命名标签处
入场动画使用 .from()(从给定值动画到当前 CSS 值),退场动画使用 .to(),两端都必须明确指定时使用 .fromTo()(最能避免重复运行时出现问题)。
gsap.to、set 和 quickTo 的区别
gsap.set(el, { autoAlpha: 0 }); // instant, no tween (autoAlpha = opacity + visibility)
const xTo = gsap.quickTo(el, "x", { duration: 0.4, ease: "power3" });
window.addEventListener("pointermove", (e) => xTo(e.clientX)); // fast repeated updates
相比直接使用 opacity,更推荐使用 autoAlpha,因为它还会在值为 0 时切换 visibility:hidden,使元素不再参与命中测试。
ScrollTrigger 基础
gsap.to(".panel", {
xPercent: -100,
ease: "none",
scrollTrigger: {
trigger: ".wrap",
start: "top top", // when trigger top hits viewport top
end: "+=2000", // 2000px of scroll distance
pin: true, // freeze .wrap while the tween plays
scrub: 1, // tie progress to scrollbar (1 = 1s catch-up smoothing)
markers: true, // dev-only visual markers — remove for prod
},
});
关键语义:
start/end接受"triggerPos viewportPos"(例如"top center")或"+=px"。scrub: true会使动画进度与滚动位置完全同步;scrub: <number>则会增加平滑延迟。- 对于非 scrub 触发器,
toggleActions: "play pause resume reverse"用于控制 onEnter/onLeave/onEnterBack/onLeaveBack 时的行为。 - 对于 scrub 补间动画,使用
ease: "none",让动效随滚动线性变化。
平滑滚动(Lenis)+ ScrollTrigger
将 Lenis(或 Locomotive)的平滑滚动与 ScrollTrigger 配合使用时,如果两者不在同一个循环中运行,就会导致不同步。Lenis 在自己的 rAF 中平滑滚动,而 ScrollTrigger 则通过 GSAP 的 ticker 读取滚动位置;如果两者独立运行,ScrollTrigger 读取到的就是过时的位置。解决方法:通过 GSAP 的 ticker 驱动 Lenis,并在每次 Lenis 滚动时更新 ScrollTrigger。
import Lenis from "lenis";
const lenis = new Lenis({ duration: 1.2, smoothWheel: true });
lenis.on("scroll", ScrollTrigger.update); // 1. update ScrollTrigger on every Lenis scroll
gsap.ticker.add((t) => lenis.raf(t * 1000)); // 2. one loop: ticker drives Lenis (seconds → ms)
gsap.ticker.lagSmoothing(0); // 3. stop GSAP catch-up jitter on heavy frames
重要:gsap.ticker 传递的时间单位是秒,而 lenis.raf() 需要的是毫秒——请乘以 1000。不要再为 Lenis 单独运行 requestAnimationFrame(raf) 循环,否则会重复驱动。
常见问题:
- 抖动 / 卡顿——残留的
requestAnimationFrame(raf)循环与 ticker 争抢,或缺少lagSmoothing(0)。移除多余循环,并将滞后平滑设为 0。 - 标记与触发器位置偏移——未将
ScrollTrigger.update订阅到lenis.on("scroll", …)。 - 固定元素异常——使用 Lenis 时不要设置
scrollerProxy(它滚动的是文档)。使用 Locomotive 时必须连接scrollerProxy和pinType,并在每个触发器上设置scroller:。 - 滚动卡住 / 方向颠倒——缺少
* 1000的单位转换,或两个循环的运行顺序错误。
在 SPA 卸载时清理:调用 gsap.ticker.remove(update) + lenis.destroy(),否则每次导航都会叠加循环。根据 prefers-reduced-motion 设置门控(跳过 Lenis,使用原生滚动)。完整的 Lenis + Locomotive 接线配置、锚点链接路由、data-lenis-prevent、React(useGSAP/useEffect),以及症状→原因对照表,详见 references/scrolltrigger-lenis.md。
SplitText(文本显现)
const split = SplitText.create(".headline", { type: "lines, words", linesClass: "line" });
gsap.from(split.lines, { yPercent: 100, opacity: 0, stagger: 0.08, duration: 0.7, ease: "power4.out" });
注意事项:
- 对行遮罩显现效果,将
overflow: hidden设置在行容器上,这样yPercent: 100才能将内容干净地隐藏起来。添加autoSplit: true(GSAP 3.13+),以便在字体加载或窗口大小调整时重新拆分。 - 重新拆分或卸载时,先调用
split.revert(),以还原原始 DOM 并避免节点重复。 - 始终在网页字体加载后再拆分(
document.fonts.ready.then(...)),以免出现错误的换行。
Flip(布局过渡)
Flip 会记录状态,让 DOM 立即变化,然后为视觉差异添加动画(FLIP 技术)。非常适合网格<->列表、展开卡片和重新挂载元素。
const state = Flip.getState(".item"); // 1. capture BEFORE
container.classList.toggle("grid"); // 2. mutate DOM/CSS (instant)
Flip.from(state, { // 3. animate the delta
duration: 0.6, ease: "power2.inOut", stagger: 0.05,
absolute: true, // take items out of flow during move (prevents reflow jitter)
});
缓动速查
power2/3.out— 入场(先快后缓)power2.inOut— 在两个屏幕状态之间移动back.out(1.7)— 超出后回弹(数字表示超出幅度)elastic.out(1, 0.3)— 弹性效果,请谨慎使用none— 由滚动控制的补间动画steps(n)— 精灵图/阶梯式运动- 自定义三次贝塞尔曲线等效写法:
CustomEase.create("x", "M0,0 C0.2,0 0,1 1,1")(CustomEase 免费)
性能与清理
- 仅为
transform(x/y/xPercent/scale/rotation)和opacity添加动画——它们由 GPU 合成,可跳过布局/绘制。避免为top/left/width/height添加动画。 - 为占用资源较多或固定定位的元素设置
will-change: transform;完成后移除。 - 使用
ScrollTrigger.batch()批量处理多个相似的滚动显现效果,而不是为每个元素单独创建触发器。 - 视口或内容发生变化后,调用
ScrollTrigger.refresh()。
SPA / 框架中的清理是必需的——遗留的触发器会导致内存泄漏和幽灵固定效果。使用 gsap.context()(或 @gsap/react 中的 useGSAP):
useEffect(() => {
const ctx = gsap.context(() => {
// all gsap + ScrollTrigger code here
}, rootRef);
return () => ctx.revert(); // kills tweens, triggers, and reverts inline styles
}, []);
交付与验证(独立 HTML)
打包的辅助脚本(
scripts/):scripts/seek-shot.sh anim.html 0 1.5 3会冻结?t=N测试工具并截取各时刻的画面;scripts/contact-sheet.sh sheet.png frame-*.png会将它们拼成一张图,便于快速检查。参见scripts/README.md。
对于自包含的动画(主视觉、显现效果、循环、微场景),交付物应是一个可直接在浏览器中打开的 HTML 文件——无需构建步骤、框架或渲染流水线。交付形式应与工作量相匹配:单文件是网页动效的合适层级;一个文件足以完成工作时,不要使用打包工具。
输出要求:
- 一个
.html文件:GSAP 和插件来自 CDN,包含你的标记和一个内联<script>中的动画。 - 所有动效都放在一个主时间轴上(
const tl = gsap.timeline())——使用单一播放头即可跳转。 - 加入下方的跳转测试工具,以便冻结任意时刻进行检查。
跳转测试工具——冻结精确时刻以便截图。 这是视频播放器定格某一帧的网页对应做法:?t=N 会将主时间轴跳转到第 N 秒并暂停,让截图落在静止且确定的画面上。
<script>
// ... build your master timeline as `tl` ...
const t = new URLSearchParams(location.search).get("t");
if (t !== null) { tl.pause(); tl.seek(parseFloat(t)); } // frozen at t seconds
// no ?t → plays normally
window.__ready = true; // ready signal for headless wait
console.log("duration", tl.duration());
</script>
验证循环——渲染 → 冻结 → 截图 → 检查:
- 在时间轴的三个时刻打开文件——开始、中间、结束:
…/anim.html?t=0、?t=<dur/2>、?t=<dur>。在控制台读取tl.duration(),获取结束时间。 - 为每个冻结的画面截图。
- 检查还原度(是否符合需求?)和瑕疵(文本被裁切、元素超出画布、字体加载前出现 FOUC、衔接处卡顿)。输出效果应当自然且完善。
任何无头截图工具都可以使用——Agent 的浏览器工具或 Playwright:
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/anim.html?t=1.2" frame-mid.png
完成前请确认:
- 可在浏览器中独立打开——没有控制台错误,也没有缺失的 CDN 资源。
- 所有动画都在一个主时间轴上;
?t=N能正确冻结画面。 - 已截取开始/中间/结束时刻的画面——符合需求且没有瑕疵。
- 已遵循
prefers-reduced-motion(简化或跳过时间轴)。 - 缓动设置合理——空间运动没有意外使用
linear。
包含完整可运行模板且已接入测试工具的文件位于 examples/standalone-template.html。
快速参考
| 目标 | API |
|---|---|
| 带重叠的序列 | tl.from(...).from(..., "-=0.3") |
| 滚动时固定 | scrollTrigger:{ pin:true, end:"+=N" } |
| 随滚动擦动 | scrollTrigger:{ scrub:1, ease:"none" } |
| 显示文本行 | SplitText.create(el,{type:"lines"}) |
| 为布局变化添加动画 | Flip.getState → mutate → Flip.from |
| 快速跟随指针 | gsap.quickTo(el,"x",{...}) |
| 全部清除 | ScrollTrigger.getAll().forEach(t=>t.kill()) |
参考文件
references/scrolltrigger-cookbook.md— 固定、擦动、水平滚动、吸附、ScrollTrigger.batch()显示效果、视差、嵌套触发器和 SPA 清理模式,附完整代码。references/scrolltrigger-lenis.md— 完整的 ScrollTrigger + Lenis/Locomotive 平滑滚动同步:规范接线方式、scrollerProxy、锚点链接、减少动态效果、React(useGSAP/useEffect)、调试检查清单,以及症状→原因对照表。examples/hero-timeline.js— 完整且可运行的 hero 入场时间线,包含 SplitText、交错显示效果和减少动态效果处理。examples/standalone-template.html— 交付模板:独立运行(通过 CDN 加载 GSAP)、单个主时间线、用于截图验证的?t=N定位测试工具,以及减少动态效果处理。