GSAP + ScrollTrigger 实现滚动驱动动画详解

GSAP(GreenSock Animation Platform)是一个强大的 JavaScript 动画库,而 ScrollTrigger 是其核心插件之一,用于创建基于滚动的动画效果(scroll-driven animations)。它允许你将动画进度与页面滚动位置绑定,实现如视差、固定元素、擦除(scrub)动画等高级交互,而无需依赖 CSS 原生滚动驱动(虽然 CSS 版本已成熟,但 GSAP 提供更灵活的控制和跨浏览器兼容)。ScrollTrigger 于 GSAP 3.x 中引入,目前(2025 年)版本已稳定在 3.12+,性能优化出色,尤其适合创意网站、scrollytelling 和交互式落地页。

相比纯 CSS 滚动驱动动画,GSAP + ScrollTrigger 的优势在于:

  • 灵活性:支持复杂时间线(Timeline)、回调函数和无缝集成其他 GSAP 插件。
  • 性能:利用 requestAnimationFrame(RAF)同步,避免主线程阻塞;内置防抖和节流。
  • 兼容:处理触屏、横向滚动和自定义滚动容器。
  • 易用:无需监听 scroll 事件,手动计算位置。

安装和基本设置

  1. 安装 GSAP:通过 npm/yarn(推荐 React/Vue 等框架)或 CDN。

    • npm: npm install gsap
    • CDN: <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
  2. 注册插件 :在代码开头添加 gsap.registerPlugin(ScrollTrigger);。这激活 ScrollTrigger,避免 tree-shaking 问题。

  3. 基本结构:ScrollTrigger 可以嵌入 GSAP tween/timeline 中,或独立创建。

    • 嵌入式:gsap.to(target, { ...animationProps, scrollTrigger: { ...config } });
    • 独立式:ScrollTrigger.create({ ...config });(用于非动画触发,如类切换或回调)。
  4. 刷新机制 :在 DOM 加载后或窗口 resize 时调用 ScrollTrigger.refresh();,以重新计算位置(尤其动态内容)。

关键概念

ScrollTrigger 的核心是基于滚动位置(而非时间)驱动动画。以下是主要术语:

概念 描述 示例值/用法
Trigger 触发元素(selector 或元素),其位置决定动画开始/结束。默认相对视口计算。 ".box"#id
Start 动画开始的滚动位置。字符串("top bottom" 表示 trigger 顶部到达视口底部)、像素值或函数。默认 "top bottom"(非 pin 时)。 "top 80%"(trigger 顶部到达视口 80% 时开始)
End 动画结束的滚动位置。类似 start,支持相对值如 "+=500"(从 start 后 500px)。默认 "bottom top"。 "+=300""bottom center"
Scrub 将动画进度与滚动直接绑定。true:即时擦除;数字(如 1):平滑追赶(秒)。 scrub: true(滚动前进/后退动画同步)
Pin 在动画活跃期固定元素。true:固定 trigger;字符串/元素:自定义目标。自动添加 pinSpacing(padding 防止内容塌陷)。 pin: true(固定 section,直到 end)
Markers 开发调试标记(绿色 start、红色 end)。布尔或对象自定义样式。 markers: true{startColor: "blue"}

其他概念:

  • Snap:滚动结束时自动吸附到指定进度(如标签或数组值),增强交互感。
  • ToggleActions:控制动画行为,如 "play pause resume reset"(进入/离开/返回/离开后)。
  • Callbacks :如 onEnteronUpdate(进度更新时执行自定义逻辑)。

配置选项

ScrollTrigger 配置是一个对象,支持丰富选项。常见包括:

  • animation:绑定的 tween 或 timeline。
  • scroller:自定义滚动容器(默认 document),如 ".custom-scroller"。
  • horizontal:true 时支持水平滚动。
  • toggleClass :激活时添加/移除类,如 {className: "active", targets: ".nav"}
  • snap :吸附配置,如 {snapTo: [0, 0.5, 1], duration: 0.5}
  • invalidateOnRefresh:true 时刷新重置动画(默认 false,性能更好)。
  • anticipatePin:true 预判快速滚动,避免 pin 延迟闪烁(3.8.0+)。
  • clamp:3.12+ 新增,确保 start/end 在边界内。

完整配置示例:

js 复制代码
scrollTrigger: {
  trigger: ".section",
  start: "top top",
  end: "+=100%",
  scrub: 1,
  pin: true,
  markers: true,
  onUpdate: self => console.log(self.progress) // 0-1 进度
}

实现步骤

  1. 准备 HTML/CSS:确保页面有足够高度(e.g., height: 300vh)以滚动。目标元素有初始样式。
  2. 创建动画:用 gsap.to/from/timeline 定义。
  3. 添加 ScrollTrigger:嵌入配置。
  4. 测试与调试:启用 markers,检查控制台。
  5. 优化 :用 ScrollTrigger.batch() 批量处理多个类似触发器(stagger 效果)。

常见示例

1. 简单淡入(Fade-In on Enter Viewport)

当元素进入视口时淡入。

js 复制代码
gsap.from(".fade-element", {
  opacity: 0,
  y: 50,
  scrollTrigger: {
    trigger: ".fade-element",
    start: "top 80%", // trigger 顶部到达视口 80% 时开始
    toggleActions: "play none none reverse" // 进入播放,离开反转
  }
});
2. 固定部分(Pinning Sections)

固定一个部分,直到滚动完指定距离。

js 复制代码
gsap.timeline({
  scrollTrigger: {
    trigger: ".pin-section",
    start: "top top",
    end: "+=500", // 固定 500px 滚动距离
    pin: true,
    scrub: true
  }
})
.to(".pin-content", { x: 300 }); // 在固定期内移动
3. 视差效果(Parallax)

背景层随滚动移动,创建深度感。

js 复制代码
gsap.to(".parallax-bg", {
  yPercent: -20, // 向下移动 20%
  ease: "none",
  scrollTrigger: {
    trigger: ".parallax-container",
    scrub: true // 直接绑定滚动
  }
});
4. 水平滚动(Horizontal Scrolling)

垂直滚动驱动水平移动,常用于画廊。

js 复制代码
const sections = gsap.utils.toArray(".horizontal-panel");
gsap.to(sections, {
  xPercent: -100 * (sections.length - 1),
  ease: "none",
  scrollTrigger: {
    trigger: ".horizontal-container",
    pin: true,
    scrub: 1,
    snap: 1 / (sections.length - 1), // 吸附到每个面板
    end: () => "+=" + document.querySelector(".horizontal-container").offsetWidth
  }
});
5. 复杂时间线(Timeline with Labels and Snap)

多步动画,按滚动进度执行。

js 复制代码
let tl = gsap.timeline({
  scrollTrigger: {
    trigger: ".container",
    pin: true,
    start: "top top",
    end: "+=500",
    scrub: 1,
    snap: { snapTo: "labels", duration: { min: 0.2, max: 3 } } // 吸附到标签
  }
});
tl.addLabel("start")
  .from(".box p", { scale: 0.3, rotation: 45, opacity: 0 })
  .addLabel("color")
  .from(".box", { backgroundColor: "#28a92b" })
  .addLabel("spin")
  .to(".box", { rotation: 360 })
  .addLabel("end");

与 Lenis 的集成(Smooth Scrolling)

Lenis 提供丝滑滚动,GSAP 可无缝集成以避免抖动:

  1. 安装 Lenis: npm install lenis

  2. 初始化 Lenis: const lenis = new Lenis(); 并在 RAF 中更新 lenis.raf(time * 1000);

  3. 代理 ScrollTrigger:

    js 复制代码
    ScrollTrigger.scrollerProxy("body", {
      scrollTop(value) { return arguments.length ? lenis.scrollTo(value, { immediate: true }) : lenis.scroll; },
      getBoundingClientRect() { return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight }; }
    });
    lenis.on("scroll", ScrollTrigger.update);
    gsap.ticker.add((time) => lenis.raf(time * 1000));
    gsap.ticker.lagSmoothing(0);

这确保 ScrollTrigger 与 Lenis 的虚拟滚动同步。避免使用 GSAP 的旧 ScrollSmoother(基于 transform),Lenis 更轻量且兼容原生滚动条。

最佳实践

  • 性能:限制数据点(<40),用 ease: "none" 于 scrub。避免在 onUpdate 中重计算。批量创建触发器以 stagger。
  • 移动端 :用 ScrollTrigger.isTouch 区分触屏逻辑。启用 fastScrollEnd 快速滚动时完成动画。
  • 响应式:用 GSAP.matchMedia()(非旧 matchMedia)创建不同断点的 ScrollTrigger。
  • 调试:markers + 控制台日志。刷新优先级(refreshPriority)确保顺序。
  • 避免问题:不 scroll-jack(劫持滚动);用 CSS scroll-snap 结合。测试快速滚动(anticipatePin)。
  • 更新注意:3.12+ 支持 clamp() 边界;3.8+ 方向吸附和 containerAnimation(嵌套横向)。
相关推荐
代码猎人2 小时前
如何实现一个三角形
前端
龙国浪子2 小时前
从点到线,从线到画:Canvas 画笔工具的实现艺术
前端·electron
代码猎人2 小时前
什么是margin重叠,如何解决
前端
TeamDev2 小时前
使用 Vue.js 构建 Java 桌面应用
java·前端·vue.js
DongHao2 小时前
跨域问题及解决方案
前端·javascript·面试
持续升级打怪中2 小时前
Vue项目中Axios全面封装实战指南
前端·javascript·vue.js
heyCHEEMS2 小时前
为什么放弃 v-if 选择 v-show?为什么组件越用越卡?
前端
百罹鸟2 小时前
【react 高频面试题—核心原理篇】:useEffect 的依赖项如果是数组或对象(引用类型),会有什么问题?如何解决?
前端·react.js·面试
hibear2 小时前
Smart Ticker - 支持任意字符的高性能文本差异动画滚动组件
前端·vue.js·react.js