在 Next.js 中集成 GSAP 动画:with-gsap 示例应用深度解析
本指南以 with-gsap 示例 为核心,系统讲解如何将专业动画库 GSAP 与 React 生态中的 react-transition-group 结合,在 Next.js 应用中实现页面级过渡与元素入场动画。读完本文,你将掌握 GSAP 在 React 组件中与 useRef/useEffect 协作的正确姿势、CSS 与动画库的配合方式,以及如何用 create-next-app --example 快速启动同类项目。
示例概览:这是怎样一个应用
with-gsap 是 Next.js 官方 examples 目录下的一个应用模板,它演示了 将 GSAP(GreenSock Animation Platform)用作 Next.js 应用内的动画库 这一核心场景。与常见的"单个组件做个小动效"不同,这个示例特意展示了两种典型的动画使用方式:
- 页面切换级动画:在页面挂载/卸载时借助
react-transition-group的过渡钩子触发 GSAP 补间; - 组件元素入场动画:组件渲染完成后,通过 React Hooks(
useRef+useEffect)驱动标题、正文等元素产生"遮罩揭幕式"的位移与淡入效果。
整个示例同时用到了两个动画相关依赖:gsap 负责高性能补间计算,react-transition-group 负责把"过渡状态(进入/退出)"转成可编程的钩子回调——两者职责互补,是理解本示例的关键切入点。
目录结构
示例采用 Pages Router(pages/ 目录)组织页面,结构如下:
examples/with-gsap/
├── components/
│ ├── Content.tsx # 正文段落组件(自身触发入场动画)
│ ├── Home.tsx # 首页内容聚合组件
│ └── Title.tsx # 两行标题组件(行级揭幕动画)
├── pages/
│ ├── _app.tsx # 应用入口,引入全局 SCSS
│ └── index.tsx # 首页路由,承载页面级 CSSTransition
├── public/
├── App.scss # 全局样式与 CSS 过渡类
├── README.md
├── package.json
└── tsconfig.json
从 pages/_app.tsx 可以看到,入口组件只做两件事:接收 AppProps 渲染当前页面组件,并在顶部引入全局样式:
import type { AppProps } from "next/app";
import "../App.scss";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
依赖与脚本一览
package.json 是本示例运行的最小依赖集合,值得逐项对照理解:
| 依赖 | 版本 | 在本示例中的作用 |
|---|---|---|
next |
latest |
应用框架(Pages Router) |
react / react-dom |
^18.2.0 |
UI 渲染基础 |
gsap |
^3.11.3 |
核心动画引擎,提供 gsap.from / gsap.to |
react-transition-group |
^4.4.5 |
暴露进入/退出过渡生命周期的容器组件 |
sass |
^1.56.2 |
编译 App.scss,让样式支持嵌套语法 |
开发依赖则补齐了 TypeScript 环境:typescript、@types/react、@types/react-dom、@types/node、@types/gsap、@types/react-transition-group。三个脚本命令分别是 next dev(开发)、next build(生产构建)与 next start(启动生产服务)。
快速开始:三种包管理器任选
根据 README 的指引,bootstrap 本示例最简单的方式是直接使用 create-next-app 的 --example 参数,它会自动把 examples/with-gsap 复制到一个全新目录并安装依赖:
# 使用 npm
npx create-next-app --example with-gsap with-gsap-app
# 使用 Yarn
yarn create next-app --example with-gsap with-gsap-app
# 使用 pnpm
pnpm create next-app --example with-gsap with-gsap-app
命令执行完后进入项目并启动开发服务器即可看到动画效果:
cd with-gsap-app
npm run dev
# 打开 http://localhost:3000
需要说明的是,示例本身也可以作为独立目录被拷贝到别处后,通过 npm install && npm run dev 直接运行。此外,也可以将应用构建后部署到支持 Next.js 的云平台(如 Vercel),示例 README 中给出了通过 create-next-app 脚手架 + 云端部署的两步走路径;npm run build 会产出优化后的生产构建产物,供 npm start 或平台部署使用。
动画实现一:页面级过渡钩子(pages/index.tsx)
页面级动画是整个示例的骨架。在 pages/index.tsx 中,Home 组件被包裹在一层 CSSTransition 中,并在两个动画回调里直接调用 GSAP:
import { CSSTransition } from "react-transition-group";
import { gsap } from "gsap";
import Home from "../components/Home";
export default function HomePage() {
const onEnter = (node: any) => {
gsap.from(
[node.children[0].firstElementChild, node.children[0].lastElementChild],
0.6,
{
y: 30,
delay: 0.6,
ease: "power3.InOut",
opacity: 0,
stagger: { amount: 0.6 },
},
);
};
const onExit = (node: any) => {
gsap.to(
[node.children[0].firstElementChild, node.children[0].lastElementChild],
0.6,
{
y: -30,
ease: "power3.InOut",
stagger: { amount: 0.2 },
},
);
};
return (
<div className="container">
<CSSTransition
in={true}
timeout={1200}
classNames="page"
onExit={onExit}
onEntering={onEnter}
unmountOnExit
>
<div className="page">
<Home />
</div>
</CSSTransition>
</div>
);
}
这段代码揭示了 GSAP 与 react-transition-group 协作的标准模式,值得逐点拆解:
onEntering/onExit回调拿到真实 DOM 节点node。node.children[0]指向.page包裹层,firstElementChild与lastElementChild则分别是Title与Content对应的外层元素——这正是 GSAP 需要的"直接操作 DOM"能力,绕过了 React 的虚拟 DOM 抽象。- 进场用
gsap.from,退场用gsap.to。进场时元素从y: 30、opacity: 0的初始状态"补间到自然状态",产生向上的浮现感;退场时则补间到y: -30,模拟内容向上卷出的效果。 stagger: { amount: 0.6 }让两个子元素依次错峰动画。入场错峰时长更长(0.6s)营造从容的序列感,退场错峰(0.2s)更快,符合"退出要干脆"的交互直觉。timeout={1200}必须覆盖动画总时长,避免 CSSTransition 提前卸载节点导致动画被截断。同时classNames="page"会让组件自动拼接.page-enter、.page-enter-active等 CSS 类,交由样式层配合处理淡入淡出。
动画实现二:组件级入场动画(Title 与 Content)
与页面级"由外部钩子驱动"不同,子组件采用了 React Hooks 的惯用法:useRef 捕获 DOM 引用,useEffect 在挂载后触发 GSAP 动画。这是把 GSAP 接入函数组件时的推荐写法。
components/Title.tsx 负责渲染两行标题,并为每一行创建独立的 gsap.from:
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
type TitleProps = {
lineContent: string;
lineContent2: string;
};
export default function Title({ lineContent, lineContent2 }: TitleProps) {
let line1 = useRef(null);
let line2 = useRef(null);
useEffect(() => {
gsap.from([line1.current, line2.current], 0.8, {
delay: 0.8,
ease: "power3.out",
y: 64,
stagger: { amount: 0.15 },
});
}, [line1, line2]);
return (
<h1 className="page-title">
<div className="line-wrap">
<div ref={line1} className="line">{lineContent}</div>
</div>
<div className="line-wrap">
<div ref={line2} className="line">{lineContent2}</div>
</div>
</h1>
);
}
关键点在于 把 ref 传入 useEffect 的依赖数组:当 ref 对象在首次渲染完成时被赋值给真实 DOM,effect 会被触发执行,此时 line1.current 已指向真实的 <div>,GSAP 才能正确读取起始位置并运行动画。动画参数同样值得留意:
- 时长
0.8s、缓动power3.out:先快后慢的收尾感更符合"揭幕"预期; delay: 0.8:等页面级淡入铺垫完成后标题再出场,形成分层节奏;y: 64配合下一节将讲到的.line-wrap { overflow: hidden },产生"文字从裁切框中升起"的遮罩揭幕效果;stagger.amount: 0.15:两行文字先后出场,间隔极短,保证视觉连贯。
正文段落 components/Content.tsx 几乎是同一模式的简化版——单个 ref、y: 24、delay: 0.9 的轻微上移淡入,节奏上比标题略晚、幅度更小,形成主次分明的叙事层级:
export default function Content() {
let line1 = useRef(null);
useEffect(() => {
gsap.from([line1.current], 0.6, {
delay: 0.9,
ease: "power3.out",
y: 24,
stagger: { amount: 0.15 },
});
}, [line1]);
return (
<p ref={line1} className="line">
A Simple example using GSAP & react-transition-group
</p>
);
}
从写法上可以提炼出本示例的通用范式:每个需要自带动画的组件都独立拥有 ref + useEffect + gsap.from 三件套。这种自包含的设计让动画逻辑就近于元素、便于复用,也天然规避了"在父组件查询子组件 DOM"的脆弱做法——这与页面级通过 CSSTransition 的 node 参数操作子元素,形成了两种互补的取舍。
CSS 层的配合:overflow 遮罩与过渡类
GSAP 负责"数值插值",但让动画具备"遮罩揭幕"质感的是 App.scss 中的样式设计:
h1 {
.line-wrap {
overflow: hidden;
height: 66px; // 固定行高作为"遮罩窗口"
}
}
每个标题行被包在 .line-wrap 中,overflow: hidden 把 y: 64 位移前的文字"藏"在可视框之外;当 GSAP 将元素从 y: 64 补间到 0,文字仿佛从盒子底部升起。这种"CSS 裁切 + JS 位移"的组合是 GSAP 场景中极常用且性能友好的技巧。
与此同时,SCSS 还定义了与 classNames="page" 配套的过渡类(.page-enter、.page-enter-active、.page-exit、.page-exit-active),负责入场/退场时 400ms 的透明度过渡:
.page-enter { opacity: 0; }
.page-enter-active {
opacity: 1;
transition: opacity 400ms;
transition-delay: 600ms; // 延迟让 GSAP 位移先走,透明度随后跟上
}
.page-exit-active {
opacity: 0;
transition: opacity 400ms;
}
值得强调的是 延迟(transition-delay: 600ms)的巧思:让 CSS 的透明过渡与 JS 的 GSAP 位移错开节拍,避免"淡入和上移同时发生"造成的生硬观感。由此可见,示例刻意划分了两条职责线:
- CSS 过渡:只处理廉价的
opacity这类纯视觉合成属性; - GSAP 补间:处理
transform: translateY(即y属性)这类更复杂的位移,并借助power3缓动获得更细腻的曲线。
关于布局还需注意一点:.container 使用了 position: relative 与固定最小宽度(min-width: 1280px),而 .page 采用 position: absolute 定位,这是为了让退出动画期间的页面离开文档流、避免布局抖动。
深入原理:为什么这样写是可靠的
综合源码可以沉淀出几条可复用的经验法则,供在真实项目中落地:
- GSAP 是命令式 DOM 动画库,必须拿到真实节点。在 React 中拿到节点的正规途径就是
ref。把ref写进useEffect依赖数组(如 Title.tsx),可以确保 effect 在节点挂载后执行——这是避免"GSAP 找不到目标元素"空转的最保险写法。 - 依赖数组应尽量收窄。
useEffect的依赖只包含ref对象本身,动画只会执行一次,符合"入场动画只播一遍"的语义。若把组件 props 也塞进依赖,可能造成动画被意外重放。 gsap.from与gsap.to的语义差异。from定义"初始状态 → 当前样式"(适合入场揭幕),to定义"当前样式 → 目标状态"(适合退场离场)。示例在进场用from、退场用to,正是基于这种语义区分。- 页面级动画交给
CSSTransition生命周期。timeout需覆盖 JS 动画总时长,onEntering/onExit等钩子与unmountOnExit配合,才能保证进出场动画不相互打架。 - 性能层面,
transform与opacity是合成器友好属性。GSAP 的y最终落为translate3d,配合will-change意识与overflow: hidden裁切,可避免退场动画触发大面积重排。
小结与延伸阅读
with-gsap 虽然只是一个几十行的小示例,却完整示范了 GSAP 在 Next.js(Pages Router)中落地的两条主线:在 CSSTransition 钩子中做页面级序列动画,以及 在组件内部用 ref + useEffect 做元素级入场动画,并配以 overflow: hidden 裁切与 CSS 过渡类作为视觉辅助。你可以基于这份代码继续扩展:把硬编码的 delay/stagger 提升为可配置 props,或接入 GSAP 的 ScrollTrigger 实现滚动驱动动画。
如果希望进一步研究本仓库中相关的脚手架能力,可对照查看 create-next-app 中 --example 参数的工作机制;若要浏览更多同类型的动画/样式示例,可查看 with-styled-components、with-framer-motion 等相邻示例目录。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00