首页
/ 在 Next.js 中集成 GSAP 动画:with-gsap 示例应用深度解析

在 Next.js 中集成 GSAP 动画:with-gsap 示例应用深度解析

2026-09-07 17:48:32作者:卓艾滢Kingsley

本指南以 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 应用内的动画库 这一核心场景。与常见的"单个组件做个小动效"不同,这个示例特意展示了两种典型的动画使用方式:

  1. 页面切换级动画:在页面挂载/卸载时借助 react-transition-group 的过渡钩子触发 GSAP 补间;
  2. 组件元素入场动画:组件渲染完成后,通过 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 节点 nodenode.children[0] 指向 .page 包裹层,firstElementChildlastElementChild 则分别是 TitleContent 对应的外层元素——这正是 GSAP 需要的"直接操作 DOM"能力,绕过了 React 的虚拟 DOM 抽象。
  • 进场用 gsap.from,退场用 gsap.to。进场时元素从 y: 30opacity: 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: 24delay: 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"的脆弱做法——这与页面级通过 CSSTransitionnode 参数操作子元素,形成了两种互补的取舍。

CSS 层的配合:overflow 遮罩与过渡类

GSAP 负责"数值插值",但让动画具备"遮罩揭幕"质感的是 App.scss 中的样式设计:

h1 {
  .line-wrap {
    overflow: hidden;
    height: 66px;   // 固定行高作为"遮罩窗口"
  }
}

每个标题行被包在 .line-wrap 中,overflow: hiddeny: 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 定位,这是为了让退出动画期间的页面离开文档流、避免布局抖动。

深入原理:为什么这样写是可靠的

综合源码可以沉淀出几条可复用的经验法则,供在真实项目中落地:

  1. GSAP 是命令式 DOM 动画库,必须拿到真实节点。在 React 中拿到节点的正规途径就是 ref。把 ref 写进 useEffect 依赖数组(如 Title.tsx),可以确保 effect 在节点挂载后执行——这是避免"GSAP 找不到目标元素"空转的最保险写法。
  2. 依赖数组应尽量收窄useEffect 的依赖只包含 ref 对象本身,动画只会执行一次,符合"入场动画只播一遍"的语义。若把组件 props 也塞进依赖,可能造成动画被意外重放。
  3. gsap.fromgsap.to 的语义差异from 定义"初始状态 → 当前样式"(适合入场揭幕),to 定义"当前样式 → 目标状态"(适合退场离场)。示例在进场用 from、退场用 to,正是基于这种语义区分。
  4. 页面级动画交给 CSSTransition 生命周期timeout 需覆盖 JS 动画总时长,onEntering / onExit 等钩子与 unmountOnExit 配合,才能保证进出场动画不相互打架。
  5. 性能层面,transformopacity 是合成器友好属性。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-componentswith-framer-motion 等相邻示例目录。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.79 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
390