发布于·20250907

GSAP ScrollTrigger 详解

038.png

在上一篇文章 GSAP 入门指南 里,我们学习了 GSAP 的两个核心:

  • Tween:补间动画。
  • Timeline:时间线。

有了它们,我们能让元素动起来。 但是,动画什么时候触发?靠谁来控制?

答案是:滚动(Scroll)

最常见的场景:

  • 元素滚动到视窗才开始播放。
  • 滚动条走到哪里,动画精确跟到哪里。
  • 内容卡住一会儿,再接着滚动,就像苹果官网。

要实现这些,我们需要今天的主角:ScrollTrigger。 它是 GSAP 官方提供的滚动插件。 一句话:把滚动条变成动画的遥控器

注册插件

我们还是用 CDN 的方式引入:

html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>

然后在 JS 里注册:

js
gsap.registerPlugin(ScrollTrigger)

不注册的话,GSAP 根本不知道有个滚动插件存在。

核心概念

ScrollTrigger 配置很多,但核心问题就两个:

  1. 什么时候开始?
  2. 怎么播放?

trigger

谁来触发?

js
gsap.to('.box', { x: 500, duration: 2, scrollTrigger: { trigger: '.box', }, })

这里 .box 就是触发点。 当 .box 出现在视口,动画才会执行。

start 和 end

动画何时开始?何时结束?

格式:"<元素位置> <视窗位置>"

  • start: "top center" → 元素顶部到达视窗中心时开始。
  • end: "+=300" → 从开始再滚动 300px,结束。

可以想象一根“滚动尺子”,startend 就是区间范围。

markers

调试神器。

js
scrollTrigger: { trigger: ".box", start: "top center", markers: true }

页面会出现彩色的 start / end 标记线。 建议开发时一直开着,肉眼确认动画区间。

scrub

ScrollTrigger 的灵魂。

  • 默认:动画触发后,按 duration 播放完。
  • scrub: true:动画进度和滚动条绑定。
js
scrub: true

滚动到一半,动画停在一半。 就像动画挂在滚动条上。

scrub: 1 → 多加 1 秒缓冲,让体验更丝滑。

pin

pin 可以让元素在滚动区间内固定。

js
pin: true

这就是网页常见的“卡住”效果。 和 CSS 的 sticky 不同,它能和动画区间深度绑定,常见的横行滚动,就是这个效果。

toggleActions

如果不用 scrub,就要靠 toggleActions

它控制四种状态:

  • onEnter
  • onLeave
  • onEnterBack
  • onLeaveBack

默认是 "play none none none"

  • 元素进入时播放一次
  • 其他情况不处理

如果你想“返回时反播”,可以设置:

js
toggleActions: 'play none none reverse'

实战演练

下面给出三个完整示例,复制到本地就能跑。 每个示例后我都会点出关键解释。

示例一:元素进入视窗

方块从左侧淡入。 进入视窗时播放,返回时反向。

html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>ScrollTrigger 示例1</title> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script> <style> body { height: 300vh; } .spacer { height: 100vh; } .box { width: 150px; height: 150px; background: #28a92b; margin-left: 50px; } </style> </head> <body> <div class="spacer"></div> <div class="box"></div> <div class="spacer"></div> <script> gsap.registerPlugin(ScrollTrigger) gsap.to('.box', { x: 600, y: 100, rotation: 360, duration: 2, scrollTrigger: { trigger: '.box', start: 'top 80%', // 元素进入视口下方 80% 时触发 end: 'bottom 20%', // 元素离开视口上方 20% 时结束 toggleActions: 'play none none reverse', markers: true, }, }) </script> </body> </html>

关键点

  • toggleActions: "play ... reverse" → 往下滚时播放,往上滚时反播。
  • markers: true → 方便调试触发区间。

示例二:视差滚动

背景比前景慢,制造 3D 深度感。

html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>ScrollTrigger 示例2</title> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script> <style> section { height: 100vh; display: flex; justify-content: center; align-items: center; font-size: 4rem; } .section-two { background: url('https://picsum.photos/1200/1200?random=1') no-repeat center/cover; height: 100vh; } </style> </head> <body> <section class="section-one"><h1>第一页</h1></section> <section class="section-two"></section> <section class="section-three"><h1>结束页</h1></section> <script> gsap.registerPlugin(ScrollTrigger) gsap.to('.section-two', { backgroundPosition: '50% 100%', scrollTrigger: { trigger: '.section-two', start: 'top bottom', // 元素顶部到达视窗底部时开始 end: 'bottom top', // 元素底部到达视窗顶部时结束 scrub: 1, // 滚动进度与动画绑定,+1秒缓冲 markers: true, }, }) </script> </body> </html>

关键点

  • scrub: 1 → 背景跟着滚动,有点延迟,更真实。
  • startend → 定义滚动区间,覆盖整个视差段落。

示例三:横向滚动

纵向滚动转为水平切换。

html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>ScrollTrigger 示例3</title> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script> <style> .horizontal-container { height: 100vh; overflow: hidden; } .panel-wrapper { height: 100%; display: flex; } .panel { flex: 0 0 100%; display: flex; align-items: center; justify-content: center; font-size: 3rem; } .panel:nth-child(1) { background: url('https://picsum.photos/1200/1200?random=1') center/cover; } .panel:nth-child(2) { background: url('https://picsum.photos/1200/1200?random=2') center/cover; } .panel:nth-child(3) { background: url('https://picsum.photos/1200/1200?random=3') center/cover; } .spacer { height: 100vh; } </style> </head> <body> <div class="spacer"></div> <section class="horizontal-container"> <div class="panel-wrapper"> <div class="panel">第一页</div> <div class="panel">第二页</div> <div class="panel">第三页</div> </div> </section> <div class="spacer"></div> <script> gsap.registerPlugin(ScrollTrigger) const wrapper = document.querySelector('.panel-wrapper') gsap.to(wrapper, { x: () => -(wrapper.scrollWidth - window.innerWidth), ease: 'none', scrollTrigger: { trigger: '.horizontal-container', pin: true, // 固定容器 scrub: 1, // 滚动驱动动画 end: () => '+=' + (wrapper.scrollWidth - window.innerWidth), invalidateOnRefresh: true, markers: true, }, }) </script> </body> </html>

关键点

  • pin: true → 容器在滚动期间固定住。
  • x: () => -(wrapper.scrollWidth - window.innerWidth) → 根据内容宽度计算移动距离。
  • invalidateOnRefresh: true → 窗口大小变化时,重新计算。

总结

ScrollTrigger 的本质是: 让滚动条变成动画的时间轴

要点:

  • 引入并注册插件。
  • triggerstartend 控制触发区间。
  • 开启 markers 调试。
  • scrubpin 是进阶玩法的核心。

有了它,你能轻松实现滚动叙事: 从淡入淡出,到视差,再到横向切换。

(完)

Discussion

欢迎交流与反馈