framework
framework/gsap/scrolltrigger.md
medium
gsap-scrolltrigger
ScrollTrigger 进视口触发与滚动驱动 — once 入场 vs scrub 联动、start/end、布局后 refresh、useGSAP 自动清理。Use when 元素滚到视口才入场 / 滚动驱动进度条或视差 / ScrollTrigger 位置算错 / 切路由后触发器残留时。
scrolltrigger进视口入场滚动驱动toggleactionsscrubstart end视口触发位置算错scrolltrigger.refresh
paths
frontend/src/animations/**/*.tsfrontend/src/**/*.tsx
GSAP · ScrollTrigger
两种用法别混
| 模式 | 配置 | 用在 |
|---|---|---|
| 一次性入场 | toggleActions 或 once: true |
卡片/区块滚到视口淡入(最常见) |
| 滚动驱动联动 | scrub: true(或数字=平滑) |
进度条、视差、长图逐帧 |
判断规则:入场动画(滚到即触发、放完即停)用 once/toggleActions;只有进度必须跟随滚动位置连续变化(进度条/视差/逐帧)才用 scrub。scrub 把每帧绑到滚动,性能与体感都更重,不是默认选项。
一次性入场(首选)
// src/animations/home.ts
import gsap from "gsap";
import { D, E } from "./tokens";
export function revealOnScroll(s: gsap.utils.Selector): void {
gsap.from(s(".rec-card"), {
y: 20,
autoAlpha: 0, // autoAlpha = opacity + visibility,避免隐藏元素仍可点
duration: D.slow,
ease: E.out,
stagger: 0.06,
scrollTrigger: {
trigger: s(".rec-grid"),
start: "top 80%", // 元素顶到达视口 80% 处触发
once: true, // 只放一次,回滚不重播
},
});
}
组件侧用 useGSAP({ scope }) 调用——scope 卸载时自动 kill 所有 ScrollTrigger,无需手动清理:
useGSAP(() => revealOnScroll(gsap.utils.selector(rootRef)), { scope: rootRef });
滚动驱动(scrub)
gsap.to(s(".progress"), {
scaleX: 1,
transformOrigin: "left center",
ease: "none", // scrub 必须 linear,否则与滚动脱节
scrollTrigger: { trigger: s("article"), start: "top top", end: "bottom bottom", scrub: true },
});
必须 refresh 的场景
DOM/布局在 ScrollTrigger 创建后变化(异步数据撑高、图片加载、字体回流、折叠展开),位置会算错:
ScrollTrigger.refresh(); // 数据/图片就位后调一次
反例
// ❌ 入场用了 scrub —— 卡片随滚动来回淡入淡出,像故障
scrollTrigger: { trigger: ".card", scrub: true } // 入场该用 once: true
// ❌ 手动 useEffect 建 ScrollTrigger 不 kill —— 切路由后残留,报错/重复触发
useEffect(() => { ScrollTrigger.create({ ... }); }, []); // 用 useGSAP({scope})
// ❌ 异步数据撑开页面后不 refresh —— start 位置停留在旧高度,触发时机全错
自检
- [ ] 入场用
once/toggleActions,不是滥用scrub? - [ ] scrub 动画
ease: "none"? - [ ] 用
useGSAP({ scope })让 ScrollTrigger 自动清理(不手动 useEffect)? - [ ] 异步内容/图片就位后调了
ScrollTrigger.refresh()? - [ ] 隐藏入场用
autoAlpha而非纯opacity?