gsap-web
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGSAP for the Web
GSAP 用于Web开发
GSAP (GreenSock Animation Platform) is the workhorse for code-driven web motion: sequenced timelines, scroll-driven storytelling, text reveals, and layout transitions. As of GSAP 3.12+, every plugin (ScrollTrigger, SplitText, Flip, MotionPath, MorphSVG, Draggable, Observer) is 100% free, including for commercial use.
GSAP(GreenSock动画平台)是代码驱动Web动效的主力工具:支持序列时间线、滚动驱动叙事、文本渐显以及布局过渡。从GSAP 3.12版本开始,所有插件(ScrollTrigger、SplitText、Flip、MotionPath、MorphSVG、Draggable、Observer)均完全免费,包括商业用途。
When to use
适用场景
- Scroll-driven storytelling: pinned sections, parallax, progress scrubbing, horizontal scroll
- Sequenced hero animations and complex multi-element timelines with precise overlap control
- Text reveals split into chars/words/lines (SplitText)
- Layout transitions where an element changes position/size/parent (Flip)
- Any motion needing fine timing control, easing precision, or imperative orchestration
- 滚动驱动叙事:固定区块、视差效果、进度绑定滚动、横向滚动
- 首屏序列动画及复杂多元素时间线,支持精确的重叠控制
- 文本拆分逐字/逐行/逐段渐显(SplitText)
- 元素位置/尺寸/父容器变化时的布局过渡(Flip)
- 任何需要精细时间控制、缓动精度或命令式编排的动效
Install and register
安装与注册
js
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
import { Flip } from "gsap/Flip";
gsap.registerPlugin(ScrollTrigger, SplitText, Flip);Plugins MUST be registered before use or they silently no-op. With a bundler, register once at app entry.
js
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
import { Flip } from "gsap/Flip";
gsap.registerPlugin(ScrollTrigger, SplitText, Flip);插件必须在使用前注册,否则会静默失效。使用打包工具时,只需在应用入口处注册一次。
Core techniques
核心技巧
Timelines first
优先使用时间线
Prefer one timeline over many independent tweens — it gives a single playhead, relative positioning, and easy reversal.
js
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 STARTPosition parameter cheat sheet:
- /
"+=0.5"— relative to the end of the timeline (gap / overlap)"-=0.3" - — start of the previous tween;
"<"— end of the previous tween">" - — 0.2s after the previous tween's start
"<0.2" - — at a named label added via
"myLabel"tl.addLabel("myLabel")
Use for entrances (animates FROM the given values TO current CSS), for exits, when both ends must be explicit (most robust against re-runs).
.from().to().fromTo()优先使用单个时间线而非多个独立补间动画——它提供统一的播放头、相对定位和便捷的反转功能。
js
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") // 在前一个动画结束前0.3秒开始
.from(".cta", { scale: 0.9, opacity: 0 }, "<"); // 与前一个动画的开始时间对齐位置参数速查:
- /
"+=0.5"—— 相对于时间线的结束位置(间隔 / 重叠)"-=0.3" - —— 前一个补间的开始;
"<"—— 前一个补间的结束">" - —— 前一个补间开始后0.2秒
"<0.2" - —— 在通过
"myLabel"添加的命名标签位置tl.addLabel("myLabel")
使用实现入场动画(从给定值动画到当前CSS状态),实现退场动画,用于需要明确两端状态的场景(在重复运行时最稳定)。
.from().to().fromTo()gsap.to vs set vs quickTo
gsap.to vs set vs quickTo
js
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 updatesautoAlphaopacityvisibility:hiddenjs
gsap.set(el, { autoAlpha: 0 }); // 即时设置,无补间动画(autoAlpha = opacity + visibility)
const xTo = gsap.quickTo(el, "x", { duration: 0.4, ease: "power3" });
window.addEventListener("pointermove", (e) => xTo(e.clientX)); // 快速重复更新优先使用而非原生,因为它在值为0时还会切换,将元素从命中测试中移除。
autoAlphaopacityvisibility:hiddenScrollTrigger basics
ScrollTrigger 基础
js
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
},
});Key semantics:
- /
starttakeend(e.g."triggerPos viewportPos") or"top center"."+=px" - locks animation progress to scroll exactly;
scrub: trueadds smoothing lag.scrub: <number> - controls onEnter/onLeave/onEnterBack/onLeaveBack for non-scrubbed triggers.
toggleActions: "play pause resume reverse" - Use for scrubbed tweens so motion tracks scroll linearly.
ease: "none"
js
gsap.to(".panel", {
xPercent: -100,
ease: "none",
scrollTrigger: {
trigger: ".wrap",
start: "top top", // 当触发器顶部碰到视口顶部时
end: "+=2000", // 2000px的滚动距离
pin: true, // 在补间动画播放时冻结.wrap元素
scrub: 1, // 将动画进度与滚动条绑定(1 = 1秒的追赶平滑效果)
markers: true, // 开发专用可视化标记——生产环境需移除
},
});关键语义:
- /
start接受end(例如"triggerPos viewportPos")或"top center"格式。"+=px" - 将动画进度完全锁定到滚动位置;
scrub: true添加平滑延迟。scrub: <number> - 控制非滚动绑定触发器的进入/离开/反向进入/反向离开行为。
toggleActions: "play pause resume reverse" - 滚动绑定的补间动画使用,使动效与滚动线性同步。
ease: "none"
Smooth scroll (Lenis) + ScrollTrigger
平滑滚动(Lenis)+ ScrollTrigger
Pairing Lenis (or Locomotive) smooth scrolling with ScrollTrigger desyncs unless both run on one loop. Lenis smooths scroll on its own rAF while ScrollTrigger reads scroll on GSAP's ticker; when they tick independently ScrollTrigger samples a stale position. The fix: drive Lenis from GSAP's ticker and update ScrollTrigger on every Lenis scroll.
js
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 framesCritical: passes time in seconds, wants milliseconds — multiply by 1000. Do NOT also run a standalone loop for Lenis; that double-drives it.
gsap.tickerlenis.raf()requestAnimationFrame(raf)Common bugs:
- Jitter/stutter — a leftover loop competing with the ticker, or missing
requestAnimationFrame(raf). Remove the rogue loop; set lag smoothing to 0.lagSmoothing(0) - Markers drift from their triggers — is not subscribed to
ScrollTrigger.update.lenis.on("scroll", …) - Pins break — under Lenis do NOT set a (it scrolls the document). Under Locomotive you MUST wire
scrollerProxy+scrollerProxyand setpinTypeon every trigger.scroller: - Scroll stuck / flipped direction — missing the unit conversion, or two loops out of order.
* 1000
Cleanup on SPA unmount: + , else loops stack per navigation. Gate behind (skip Lenis, run native scroll). Full Lenis + Locomotive wiring, anchor-link routing, , React (/), and a symptom→cause table are in .
gsap.ticker.remove(update)lenis.destroy()prefers-reduced-motiondata-lenis-preventuseGSAPuseEffectreferences/scrolltrigger-lenis.md将Lenis(或Locomotive)平滑滚动与ScrollTrigger结合时,若两者不在同一循环中运行会导致不同步。Lenis在自身的rAF中处理平滑滚动,而ScrollTrigger在GSAP的ticker中读取滚动位置;当两者独立运行时,ScrollTrigger会读取到过时的位置。解决方法:通过GSAP的ticker驱动Lenis,并在每次Lenis滚动时更新ScrollTrigger。
js
import Lenis from "lenis";
const lenis = new Lenis({ duration: 1.2, smoothWheel: true });
lenis.on("scroll", ScrollTrigger.update); // 1. 每次Lenis滚动时更新ScrollTrigger
gsap.ticker.add((t) => lenis.raf(t * 1000)); // 2. 统一循环:ticker驱动Lenis(秒 → 毫秒)
gsap.ticker.lagSmoothing(0); // 3. 在高负载帧时停止GSAP的追赶抖动关键注意事项:传递的时间单位是秒,需要毫秒——需乘以1000。不要同时为Lenis运行独立的循环,否则会导致双重驱动。
gsap.tickerlenis.raf()requestAnimationFrame(raf)常见问题:
- 抖动/卡顿 —— 存在与ticker冲突的残留循环,或未设置
requestAnimationFrame(raf)。移除多余循环;将延迟平滑设置为0。lagSmoothing(0) - 标记偏移 —— 未将订阅到
ScrollTrigger.update。lenis.on("scroll", …) - 固定功能失效 —— 在Lenis下不要设置(它滚动整个文档)。在Locomotive下必须配置
scrollerProxy+scrollerProxy,并为每个触发器设置pinType。scroller: - 滚动卡住/方向反转 —— 缺少的单位转换,或两个循环顺序错误。
* 1000
SPA卸载时的清理: + ,否则每次导航都会叠加循环。根据进行适配(跳过Lenis,使用原生滚动)。完整的Lenis + Locomotive配置、锚点链接路由、、React(/)以及问题排查表请参考。
gsap.ticker.remove(update)lenis.destroy()prefers-reduced-motiondata-lenis-preventuseGSAPuseEffectreferences/scrolltrigger-lenis.mdSplitText (text reveal)
SplitText(文本渐显)
js
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" });Gotchas:
- Wrap line-mask reveals: set on the line wrapper so
overflow: hiddenhides cleanly. AddyPercent: 100(GSAP 3.13+) to re-split on font load / resize.autoSplit: true - Call before re-splitting or on unmount to restore original DOM and avoid duplicated nodes.
split.revert() - Always split AFTER web fonts load () to prevent wrong line breaks.
document.fonts.ready.then(...)
js
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(GSAP 3.13+)可在字体加载/窗口 resize 时重新拆分文本。autoSplit: true - 在重新拆分或卸载前调用,恢复原始DOM结构,避免节点重复。
split.revert() - 务必在网页字体加载完成后再拆分文本(),防止换行错误。
document.fonts.ready.then(...)
Flip (layout transitions)
Flip(布局过渡)
Flip records state, lets the DOM change instantly, then animates the visual difference (FLIP technique). Ideal for grid<->list, expanding cards, and reparenting.
js
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)
});Flip会记录元素状态,允许DOM即时变化,然后动画展示视觉差异(FLIP技术)。非常适合网格<->列表切换、卡片展开以及元素重父化场景。
js
const state = Flip.getState(".item"); // 1. 捕获变化前的状态
container.classList.toggle("grid"); // 2. 即时修改DOM/CSS
Flip.from(state, { // 3. 动画展示差异
duration: 0.6, ease: "power2.inOut", stagger: 0.05,
absolute: true, // 移动过程中将元素移出文档流(防止重排抖动)
});Easing quick guide
缓动速查
- — entrances (fast then settle)
power2/3.out - — moves between two on-screen states
power2.inOut - — overshoot/pop (number = overshoot amount)
back.out(1.7) - — bouncy, use sparingly
elastic.out(1, 0.3) - — scrubbed scroll tweens
none - — sprite/stepped motion
steps(n) - Custom cubic-bezier equivalent: (CustomEase is free)
CustomEase.create("x", "M0,0 C0.2,0 0,1 1,1")
- —— 入场动画(快速启动后减速)
power2/3.out - —— 屏幕内状态切换动画
power2.inOut - —— 过冲/弹出效果(数值表示过冲量)
back.out(1.7) - —— 弹性效果,谨慎使用
elastic.out(1, 0.3) - —— 滚动绑定的补间动画
none - —— 帧动画/步进动效
steps(n) - 自定义贝塞尔曲线等价方案:(CustomEase免费)
CustomEase.create("x", "M0,0 C0.2,0 0,1 1,1")
Performance and cleanup
性能与清理
- Animate (
transform) andx/y/xPercent/scale/rotationonly — they are GPU-composited and skip layout/paint. Avoid animatingopacity.top/left/width/height - Set on heavy/pinned elements; remove after.
will-change: transform - Batch many similar scroll reveals with instead of one trigger per element.
ScrollTrigger.batch() - After a viewport/content change, call .
ScrollTrigger.refresh()
SPA / framework cleanup is mandatory — orphaned triggers cause memory leaks and ghost pinning. Use (or from ):
gsap.context()useGSAP@gsap/reactjs
useEffect(() => {
const ctx = gsap.context(() => {
// all gsap + ScrollTrigger code here
}, rootRef);
return () => ctx.revert(); // kills tweens, triggers, and reverts inline styles
}, []);- 仅动画(
transform)和x/y/xPercent/scale/rotation——它们由GPU合成,跳过布局/绘制阶段。避免动画opacity。top/left/width/height - 对复杂/固定元素设置;动画结束后移除该属性。
will-change: transform - 使用批量处理多个相似的滚动渐显效果,而非为每个元素单独创建触发器。
ScrollTrigger.batch() - 视口/内容变化后,调用。
ScrollTrigger.refresh()
SPA/框架中的清理操作至关重要——孤立的触发器会导致内存泄漏和幽灵固定问题。使用(或中的):
gsap.context()@gsap/reactuseGSAPjs
useEffect(() => {
const ctx = gsap.context(() => {
// 所有gsap + ScrollTrigger代码写在此处
}, rootRef);
return () => ctx.revert(); // 销毁补间、触发器并恢复内联样式
}, []);Deliver & verify (standalone HTML)
交付与验证(独立HTML)
Packaged helper ():scripts/freezes thescripts/seek-shot.sh anim.html 0 1.5 3harness and screenshots each moment;?t=Ntiles them for one-glance review. Seescripts/contact-sheet.sh sheet.png frame-*.png.scripts/README.md
For a self-contained animation (hero, reveal, loop, micro-scene) the deliverable is one HTML file that opens directly in a browser — no build step, no framework, no render pipeline. Match the deliverable to the weight of the work: a single file is the right tier for web motion; don't reach for a bundler when one file does the job.
Output contract:
- One file: GSAP + plugins from CDN, your markup, and the animation in one inline
.html.<script> - All motion on one master timeline () — a single playhead you can seek.
const tl = gsap.timeline() - Include the seek harness below so any moment can be frozen for inspection.
Seek harness — freeze an exact moment for screenshots. The web parallel of a video player's frame-pin: seeks the master timeline to seconds and pauses, so a screenshot lands on a still, deterministic frame.
?t=NNhtml
<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>Verify loop — render → freeze → screenshot → check:
- Open the file at three moments across the timeline — start, mid, end:
,
…/anim.html?t=0,?t=<dur/2>. Read?t=<dur>from the console for the end time.tl.duration() - Screenshot each frozen frame.
- Check both fidelity (does it match the brief?) and artifacts (clipped text, elements off-canvas, FOUC before fonts load, jank at seams). Output should look intentional and finished.
Any headless screenshot tool works — your agent's browser tool, or Playwright:
bash
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/anim.html?t=1.2" frame-mid.pngBefore you finish:
- Opens standalone in a browser — no console errors, no missing CDN.
- All animation on one master timeline; freezes correctly.
?t=N - Screenshotted at start / mid / end — matches the brief, no artifacts.
- honored (timeline simplified or skipped).
prefers-reduced-motion - Easing is intentional — no accidental on spatial motion.
linear
A complete runnable template with the harness wired in is in .
examples/standalone-template.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文件——无需构建步骤、无需框架、无需渲染流水线。交付物的复杂度应与工作量匹配:单个文件是Web动效的合适交付形式;能通过单个文件完成的工作,无需使用打包工具。
输出规范:
- 单个文件:包含CDN引入的GSAP+插件、你的标记代码以及内联
.html中的动画逻辑。<script> - 所有动效基于单个主时间线()——可通过单个播放头控制进度。
const tl = gsap.timeline() - 包含以下时间线定位工具,以便冻结任意时刻进行检查。
时间线定位工具——冻结精确时刻用于截图。 类似视频播放器的帧锁定功能:会将主时间线定位到第N秒并暂停,确保截图是静态的、确定的帧。
?t=Nhtml
<script>
// ... 在此构建主时间线`tl` ...
const t = new URLSearchParams(location.search).get("t");
if (t !== null) { tl.pause(); tl.seek(parseFloat(t)); } // 冻结在第t秒
// 无?t参数时正常播放
window.__ready = true; // 无头工具就绪信号
console.log("duration", tl.duration());
</script>验证流程——渲染→冻结→截图→检查:
- 在时间线的三个时刻打开文件:开始、中间、结束:
,
…/anim.html?t=0,?t=<dur/2>。从控制台读取?t=<dur>获取结束时间。tl.duration() - 截取每个冻结帧的截图。
- 检查还原度(是否符合需求?)和瑕疵(文本截断、元素超出画布、字体加载前的FOUC、接缝处卡顿)。输出结果应看起来是精心设计且完整的。
任何无头截图工具均可使用——你的Agent浏览器工具,或Playwright:
bash
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.htmlQuick reference
速查表
| Goal | API |
|---|---|
| Sequence with overlap | |
| Pin while scrolling | |
| Scrub to scroll | |
| Reveal lines of text | |
| Animate layout change | |
| Fast pointer follow | |
| Kill everything | |
| 目标 | API |
|---|---|
| 带重叠的序列动画 | |
| 滚动时固定元素 | |
| 动画进度绑定滚动 | |
| 逐行文本渐显 | |
| 布局变化动画 | |
| 快速指针跟随 | |
| 销毁所有触发器 | |
Reference files
参考文件
- — pin, scrub, horizontal scroll, snap,
references/scrolltrigger-cookbook.mdreveals, parallax, nested triggers, and SPA cleanup patterns with full code.ScrollTrigger.batch() - — full ScrollTrigger + Lenis/Locomotive smooth-scroll sync: canonical wiring,
references/scrolltrigger-lenis.md, anchor links, reduced-motion, React (scrollerProxy/useGSAP), debugging checklist, and symptom→cause table.useEffect - — a complete, runnable hero entrance timeline with SplitText, staggered reveals, and reduced-motion handling.
examples/hero-timeline.js - — the deliverable template: self-contained (CDN GSAP), one master timeline, the
examples/standalone-template.htmlseek harness for screenshot verification, and reduced-motion handling.?t=N
- —— 固定、滚动绑定、横向滚动、吸附、
references/scrolltrigger-cookbook.md渐显、视差、嵌套触发器以及SPA清理模式的完整代码。ScrollTrigger.batch() - —— ScrollTrigger + Lenis/Locomotive平滑滚动同步的完整指南:标准配置、
references/scrolltrigger-lenis.md、锚点链接、减少动效适配、React(scrollerProxy/useGSAP)、调试清单以及问题排查表。useEffect - —— 完整可运行的首屏入场时间线,包含SplitText、交错渐显和减少动效处理。
examples/hero-timeline.js - —— 交付模板:独立运行(CDN引入GSAP)、单个主时间线、用于截图验证的
examples/standalone-template.html定位工具以及减少动效处理。?t=N