关于 gsap-frameworks
用于在 Vue、Nuxt、Svelte 等具有生命周期的组件框架中编写和审查 GSAP 动画的技能,重点是 DOM 就绪后初始化、选择器作用域和清理;React 项目应使用 gsap-react。
主要功能
- 01挂载后创建动画
- 02将选择器限定在组件内
- 03卸载时还原动画
- 04清理 ScrollTrigger 实例
- 05按需加载 Nuxt 插件
适用场景
- 01为 Vue 组件添加 GSAP 动画
- 02编写生命周期安全的 Svelte 动画
- 03在组件销毁时管理 ScrollTrigger
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nexu-io/ open-design --skill gsap-frameworks安装后在 Claude Code 中输入/gsap-frameworks确认已加载。
附带文件LICENSESOURCES.md
详细介绍
GSAP 与 Vue、Svelte 及其他框架
整理自 GreenSock 官方 GSAP 技能:https://github.com/greensock/gsap-skills
何时使用此技能
在 Vue(或 Nuxt)、Svelte(或 SvelteKit)或其他使用生命周期(挂载/卸载)的组件框架中编写或审查 GSAP 代码时使用。对于 React,请使用 gsap-react(useGSAP hook、gsap.context())。
**相关技能:**补间动画和时间线请使用 gsap-core 和 gsap-timeline;基于滚动的动画请使用 gsap-scrolltrigger;React 请使用 gsap-react。
原则(适用于所有框架)
- 在组件的 DOM 可用后(例如 onMounted、onMount)创建补间动画和 ScrollTriggers。
- 在卸载(或等效阶段)时销毁或还原它们,确保不会在已脱离的节点上运行,也不会造成泄漏。
- 将选择器限定在组件根元素内,这样
.box等选择器只会匹配该组件中的元素,而不会匹配页面其他部分的元素。
Vue 3(Composition API)
如需查看演示这些模式的可运行 Vite + Vue 3 项目,请参阅上游示例:https://github.com/greensock/gsap-skills/tree/aed9cfd3277740755f6bfc1155c7aa645403b760/examples/vue。
使用 onMounted 在组件进入 DOM 后运行 GSAP。使用 onUnmounted 进行清理。
import { onMounted, onUnmounted, ref } from "vue";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger); // once per app, e.g. in main.js
export default {
setup() {
const container = ref(null);
let ctx;
onMounted(() => {
if (!container.value) return;
ctx = gsap.context(() => {
gsap.to(".box", { x: 100, duration: 0.6 });
gsap.from(".item", { autoAlpha: 0, y: 20, stagger: 0.1 });
}, container.value);
});
onUnmounted(() => {
ctx?.revert();
});
return { container };
},
};
- ✅ gsap.context(scope) — 将容器 ref(例如
container.value)作为第二个参数传入,这样.item等选择器就会限定在该根元素内。回调中创建的所有动画和 ScrollTriggers 都会被追踪,并在调用 ctx.revert() 时还原。 - ✅ onUnmounted — 始终调用 ctx.revert(),以销毁补间动画和 ScrollTriggers,并还原内联样式。
Vue 3(script setup)
在 <script setup> 中使用 ref,思路相同:
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
const container = ref(null);
let ctx;
onMounted(() => {
if (!container.value) return;
ctx = gsap.context(() => {
gsap.to(".box", { x: 100 });
gsap.from(".item", { autoAlpha: 0, stagger: 0.1 });
}, container.value);
});
onUnmounted(() => {
ctx?.revert();
});
</script>
<template>
<div ref="container">
<div class="box">Box</div>
<div class="item">Item</div>
</div>
</template>
Nuxt 4
如需查看包含插件注册、延迟加载和 SSR 安全模式的可运行 Nuxt 4 项目,请参阅 https://github.com/greensock/gsap-skills/tree/aed9cfd3277740755f6bfc1155c7aa645403b760/examples/nuxt。
使用可复用的组合式函数注册 GSAP 插件,并延迟加载应用中不常用的插件:
// composables/useGSAP.ts
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
// In order to dynamically load all the GSAP plugins
const pluginMap = {
CustomEase: () => import("gsap/CustomEase"),
Draggable: () => import("gsap/Draggable"),
CSSRulePlugin: () => import("gsap/CSSRulePlugin"),
EaselPlugin: () => import("gsap/EaselPlugin"),
EasePack: () => import("gsap/EasePack"),
Flip: () => import("gsap/Flip"),
MotionPathPlugin: () => import("gsap/MotionPathPlugin"),
Observer: () => import("gsap/Observer"),
PixiPlugin: () => import("gsap/PixiPlugin"),
ScrollToPlugin: () => import("gsap/ScrollToPlugin"),
ScrollTrigger: () => import("gsap/ScrollTrigger"),
TextPlugin: () => import("gsap/TextPlugin"),
DrawSVGPlugin: () => import("gsap/DrawSVGPlugin"),
Physics2DPlugin: () => import("gsap/Physics2DPlugin"),
PhysicsPropsPlugin: () => import("gsap/PhysicsPropsPlugin"),
ScrambleTextPlugin: () => import("gsap/ScrambleTextPlugin"),
CustomBounce: () => import("gsap/CustomBounce"),
CustomWiggle: () => import("gsap/CustomWiggle"),
GSDevTools: () => import("gsap/GSDevTools"),
InertiaPlugin: () => import("gsap/InertiaPlugin"),
MorphSVGPlugin: () => import("gsap/MorphSVGPlugin"),
MotionPathHelper: () => import("gsap/MotionPathHelper"),
ScrollSmoother: () => import("gsap/ScrollSmoother"),
SplitText: () => import("gsap/SplitText"),
} as const;
type PluginMap = typeof pluginMap;
type LoadablePlugin = keyof PluginMap;
// Resolves the module type for a given key, then picks the named export matching the key
// this allows to have the type definitions for autocomplete in your code editor
type PluginModule<K extends LoadablePlugin> = Awaited<ReturnType<PluginMap[K]>>;
type PluginExport<K extends LoadablePlugin> = PluginModule<K>[K & keyof PluginModule<K>];
export default function () {
// Register all the GSAP Plugins you want at this point
gsap.registerPlugin(ScrollTrigger);
/*
If you want to lazy load some of the plugins that are
not widely used in your app (for example in just a couple
of components or a single route), you can use this method
*/
async function lazyLoadPlugin<K extends LoadablePlugin>(plugin: K): Promise<PluginExport<K>> {
const loader = pluginMap[plugin];
const m = await loader();
const p = (m as any)[plugin];
gsap.registerPlugin(p);
return p;
}
return {
gsap,
ScrollTrigger,
lazyLoadPlugin,
};
}
通过组件中的 useGSAP() 访问:
const { gsap, ScrollTrigger, lazyLoadPlugin } = useGSAP();
- ✅
useGSAP()提供对 gsap 实例和延迟加载方法的类型化访问。 - ✅ 对应用中不常用的插件(SplitText、MorphSVG 等)进行延迟加载,以减小初始打包体积。
- ✅ 在组件中使用 gsap.context(scope) 和 onUnmounted → ctx.revert(),与 Vue 3 中的做法相同。
Svelte
使用 onMount 在 DOM 就绪后运行 GSAP。使用 onMount 返回的清理函数(或跟踪 context 并在响应式代码块/组件销毁时清理)进行还原。Svelte 5 使用不同的生命周期;原则相同:在“已挂载”时创建,在“已销毁”时还原。
<script>
import { onMount } from "svelte";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
let container;
onMount(() => {
if (!container) return;
const ctx = gsap.context(() => {
gsap.to(".box", { x: 100 });
gsap.from(".item", { autoAlpha: 0, stagger: 0.1 });
}, container);
return () => ctx.revert();
});
</script>
<div bind:this={container}>
<div class="box">Box</div>
<div class="item">Item</div>
</div>
- ✅ bind:this={container} — 获取根元素的引用,以便将其传给 gsap.context(scope)。
- ✅ return () => ctx.revert() — Svelte 的 onMount 可以返回清理函数;在其中调用 ctx.revert(),以便在组件销毁时执行清理。
限定选择器范围
不要使用可能匹配当前组件之外元素的全局选择器。始终将 scope(容器元素或 ref)作为第二个参数传给 gsap.context(callback, scope),这样在回调中运行的任何选择器都只会在该子树中查找。
- ✅ gsap.context(() => { gsap.to(".box", ...) }, containerRef) —
.box只会在containerRef内查找。 - ❌ 在组件中不带 context 作用域运行 gsap.to(".box", ...),可能会影响其他实例或页面的其余部分。
ScrollTrigger 清理
使用补间动画/时间轴上的 scrollTrigger 配置或调用 ScrollTrigger.create() 时,会创建 ScrollTrigger 实例。它们会被 gsap.context() 包含在内,并在调用 ctx.revert() 时还原。因此:
- 在与补间动画相同的 gsap.context() 回调中创建 ScrollTrigger。
- 在会影响触发器位置的布局变化之后(例如数据加载完成后)调用 ScrollTrigger.refresh();在 Vue/Svelte 中,这通常意味着要等到 DOM 更新之后(例如在 Vue 中使用 nextTick、在 Svelte 中使用 tick,或在异步内容加载完成后)。
何时创建与销毁
| 生命周期 | 操作 |
|---|---|
| 挂载 | 在 gsap.context(scope) 中创建补间动画和 ScrollTrigger。 |
| 卸载/销毁 | 调用 ctx.revert(),以销毁该上下文中的所有动画和 ScrollTrigger,并还原内联样式。 |
不要在组件的 setup 中创建 GSAP 动画,也不要在根元素存在之前运行的同步顶层脚本中创建。请等待 onMounted / onMount(或等效钩子),确保容器引用已在 DOM 中。
请勿
- ❌ 在组件挂载之前创建补间动画或 ScrollTrigger(例如,在 setup 中不使用 onMounted);此时 DOM 节点可能尚不存在。
- ❌ 不带 scope 使用选择器字符串(将容器作为第二个参数传给 gsap.context()),否则选择器可能会匹配组件外部的元素。
- ❌ 跳过清理;始终在 onUnmounted 中调用 ctx.revert(),或在 onMount 的返回函数中调用,以便在组件销毁时销毁动画和 ScrollTrigger。
- ❌ 在每次渲染都会运行的组件主体中注册插件(这不会造成问题,只是浪费);请在应用级别注册一次。
了解更多
- 面向 React 特定模式(useGSAP、contextSafe)的 gsap-react 技能。