Communitygithub.com

GSAP 与 ScrollTrigger 动画开发技能

用于 GSAP(GreenSock 动画平台)及 ScrollTrigger 插件的综合技能。在创建 Web 动画、滚动驱动体验、时间线、补间动画、滚动触发动画、固定、擦拭、视差效果或动画化 DOM 元素、SVG、Canvas、WebGL 或 Three.js 时使用。当任务涉及 GSAP、ScrollTrigger、平滑动画、滚动效果或动画排序时触发。

GSAP 与 ScrollTrigger 动画开发技能 是什么?

该技能为 AI 助手(如 Claude Code、Cursor)提供了全面的指导,用于开发和调试基于 GSAP(GreenSock 动画平台)和 ScrollTrigger 插件的 Web 动画。它涵盖了从基础补间动画、时间线编排到高级滚动驱动动画的所有内容,包括固定元素、水平滚动、视差效果、图片序列擦拭以及 React 和 Three.js 集成。技能文档详细解释了核心概念,如 `gsap.to()`、`gsap.from()`、`gsap.fromTo()`、时间线位置参数以及 ScrollTrigger 的 `start`/`end` 位置、`scrub` 和 `toggleActions` 属性。它还提供了按钮式代码片段和常见模式,如淡入、重新播放动画、动画批处理和错开动画。此外,该技能还包含了与 Locomotive Scroll 的无缝集成、性能最佳实践(CSS `will-change`、限制重绘、清理 ScrollTrigger)以及常见陷阱的故障排除,使其成为任何开发者在生产级动画项目中不可或缺的资源。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Samueladewole/claude-skills/tree/HEAD/gsap-scrolltrigger

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

GSAP 与 ScrollTrigger 开发

概述

GSAP(GreenSock 动画平台)是业界领先的 JavaScript 动画库,用于创建高性能、生产级的动画。ScrollTrigger 是 GSAP 强大的插件,用于滚动驱动的动画。它们协同工作,可以实现从简单的 UI 过渡到复杂的基于滚动讲述故事的各种体验。

核心概念

基础:补间动画(Tweens)

补间动画是从点 A 到点 B 的单个动画。

// 动画到某个状态(从当前状态开始)
gsap.to(".box", {
  x: 200,
  rotation: 360,
  duration: 1,
  ease: "power2.inOut"
});

// 从某个状态开始动画(到当前状态)
gsap.from(".box", {
  opacity: 0,
  y: -50,
  duration: 0.8
});

// 从某个状态到某个状态(定义起点和终点)
gsap.fromTo(".box",
  { opacity: 0, scale: 0.5 }, // 起始状态
  { opacity: 1, scale: 1, duration: 1 } // 结束状态
);

时间线:编排动画序列

时间线(Timelines) 协调多个补间动画的序列或重叠。

const tl = gsap.timeline();

// 默认顺序执行
tl.to(".box1", { x: 100, duration: 1 })
  .to(".box2", { y: 100, duration: 1 })
  .to(".box3", { rotation: 360, duration: 1 });

// 使用标签进行组织
tl.addLabel("start")
  .to(".hero", { opacity: 1, duration: 1 })
  .addLabel("reveal")
  .to(".content", { y: 0, duration: 0.8 }, "reveal") // 从“reveal”标签处开始
  .to(".cta", { scale: 1, duration: 0.5 }, "reveal+=0.5"); // 在“reveal”标签后 0.5 秒

位置参数(时间线定时)

控制动画在时间线内何时开始:

const tl = gsap.timeline();

// 默认:逐个执行
tl.to(".box1", { x: 100 })
  .to(".box2", { x: 100 }); // 等 box1 结束后开始

// 同时开始
tl.to(".box1", { x: 100 })
  .to(".box2", { y: 100 }, 0); // 在 0 秒处开始

// 相对定位
tl.to(".box1", { x: 100, duration: 2 })
  .to(".box2", { y: 100 }, "-=1"); // 在 box1 结束前 1 秒开始
  .to(".box3", { rotation: 360 }, "+=0.5"); // 在 box2 结束后 0.5 秒开始

// 在特定时间点
tl.to(".box1", { x: 100 }, 2.5); // 在 2.5 秒处开始

ScrollTrigger 基础

基本滚动动画

gsap.registerPlugin(ScrollTrigger);

gsap.to(".box", {
  x: 500,
  scrollTrigger: {
    trigger: ".box",
    start: "top center", // 当触发元素的顶部到达视口中心时
    end: "bottom center",
    markers: true, // 仅在开发阶段使用 - 显示起始/结束位置
    scrub: true, // 将动画与滚动条关联
    toggleActions: "play none none reverse" // onEnter onLeave onEnterBack onLeaveBack
  }
});

起始与结束位置

格式:"[触发器位置] [视口位置]"

// 常见模式
start: "top top"      // 触发器顶部碰到视口顶部
start: "top center"   // 触发器顶部碰到视口中心(默认)
start: "top bottom"   // 触发器顶部碰到视口底部
start: "center center" // 触发器中心碰到视口中心

// 带偏移量
start: "top top+=100"   // 视口顶部下方 100px
start: "top 80%"        // 视口往下 80% 的位置
end: "+=500"            // 起始位置之后 500px
end: "bottom top"       // 触发器底部碰到视口顶部

擦拭(滚动同步动画)

// 布尔值:直接连接到滚动条(立即生效)
scrub: true

// 数字:平滑延迟时间(秒)
scrub: 1  // 需要 1 秒来“追上”滚动条
scrub: 0.5 // 更快、更紧凑的感觉

切换动作

在四个滚动点控制动画:

toggleActions: "play pause resume reset"
// onEnter | onLeave | onEnterBack | onLeaveBack

// 动作:play, pause, resume, restart, reset, complete, reverse, none

常见模式:

toggleActions: "play none none none"       // 进入时播放一次
toggleActions: "play none none reverse"    // 向前播放,向后反转
toggleActions: "play complete reverse reset" // 完全控制
toggleActions: "restart pause resume pause"  // 每次进入时重新开始

常见模式

1. 滚动时淡入

gsap.from(".fade-in", {
  opacity: 0,
  y: 50,
  duration: 1,
  scrollTrigger: {
    trigger: ".fade-in",
    start: "top 80%",
    end: "top 50%",
    scrub: 1,
    once: true // 仅动画一次
  }
});

2. 滚动时固定元素

ScrollTrigger.create({
  trigger: ".panel",
  start: "top top",
  end: "+=500", // 在 500px 的滚动距离内保持固定
  pin: true,
  pinSpacing: true // 添加间距(默认 true)
});

3. 水平滚动区域

const sections = gsap.utils.toArray(".panel");

gsap.to(sections, {
  xPercent: -100 * (sections.length - 1),
  ease: "none",
  scrollTrigger: {
    trigger: ".container",
    pin: true,
    scrub: 1,
    end: () => "+=" + document.querySelector(".container").offsetWidth
  }
});

4. 视差效果

// 较慢移动(背景层)
gsap.to(".bg", {
  y: 200,
  ease: "none",
  scrollTrigger: {
    trigger: ".section",
    start: "top bottom",
    end: "bottom top",
    scrub: true
  }
});

// 较快移动(前景层)
gsap.to(".fg", {
  y: -100,
  ease: "none",
  scrollTrigger: {
    trigger: ".section",
    start: "top bottom",
    end: "bottom top",
    scrub: true
  }
});

5. 滚动触发的时间线

const tl = gsap.timeline({
  scrollTrigger: {
    trigger: ".container",
    start: "top top",
    end: "+=500",
    scrub: 1,
    pin: true,
    snap: {
      snapTo: "labels", // 吸附到时间线标签
      duration: { min: 0.2, max: 3 },
      delay: 0.2,
      ease: "power1.inOut"
    }
  }
});

tl.addLabel("start")
  .from(".title", { scale: 0.3, rotation: 45, autoAlpha: 0 })
  .addLabel("color")
  .from(".box", { backgroundColor: "#28a92b" })
  .addLabel("spin")
  .to(".box", { rotation: 360 })
  .addLabel("end");

6. 批处理动画(多个元素)

// 遍历多个元素
gsap.utils.toArray(".box").forEach((box, i) => {
  gsap.from(box, {
    y: 100,
    opacity: 0,
    scrollTrigger: {
      trigger: box,
      start: "top 80%",
      end: "top 50%",
      scrub: 1
    }
  });
});

// 或者使用 ScrollTrigger.batch
ScrollTrigger.batch(".box", {
  onEnter: batch => gsap.to(batch, { opacity: 1, y: 0, stagger: 0.15 }),
  onLeave: batch => gsap.set(batch, { opacity: 0 }),
  start: "top 80%",
  once: true
});

7. 错开动画

gsap.from(".item", {
  y: 50,
  opacity: 0,
  duration: 0.8,
  stagger: 0.1, // 每个项目之间的间隔为 0.1 秒
  scrollTrigger: {
    trigger: ".grid",
    start: "top 80%"
  }
});

// 高级错开
gsap.from(".item", {
  scale: 0,
  duration: 1,
  stagger: {
    each: 0.1,
    from: "center", // "start", "center", "end", "edges" 或索引数字
    grid: "auto", // 用于网格布局
    ease: "power2.inOut"
  }
});

集成模式

与 Three.js / WebGL 集成

import * as THREE from 'three';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

// 动画化相机
gsap.to(camera.position, {
  x: 5,
  y: 3,
  z: 10,
  scrollTrigger: {
    trigger: "#section2",
    start: "top top",
    end: "bottom top",
    scrub: 1,
    onUpdate: () => camera.lookAt(scene.position)
  }
});

// 动画化网格旋转
gsap.to(mesh.rotation, {
  y: Math.PI * 2,
  scrollTrigger: {
    trigger: "#section3",
    start: "top bottom",
    end: "bottom top",
    scrub: true
  }
});

// 动画化材质属性
gsap.to(material, {
  opacity: 0,
  scrollTrigger: {
    trigger: "#section4",
    start: "top center",
    end: "center center",
    scrub: 1
  }
});

与 React 集成(useGSAP Hook)

import { useRef } from 'react';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

function Component() {
  const container = useRef();
  const box = useRef();

  useGSAP(() => {
    gsap.to(box.current, {
      x: 200,
      scrollTrigger: {
        trigger: box.current,
        start: "top center",
        end: "bottom center",
        scrub: true,
        markers: true
      }
    });
  }, { scope: container }); // 作用域用于清理

  return (
    <div ref={container}>
      <div ref={box} className="box">动画盒子</div>
    </div>
  );
}

在 React 中共享时间线

function App() {
  const [tl, setTl] = useState();

  useGSAP(() => {
    const timeline = gsap.timeline();
    setTl(timeline);
  }, []);

  return (
    <div>
      <Box timeline={tl} index={0} />
      <Circle timeline={tl} index={1} />
    </div>
  );
}

function Box({ timeline, index }) {
  const ref = useRef();

  useGSAP(() => {
    timeline && timeline.to(ref.current, { x: 100 }, index * 0.1);
  }, [timeline, index]);

  return <div ref={ref} className="box" />;
}

Locomotive Scroll 集成

import LocomotiveScroll from 'locomotive-scroll';

const scroller = new LocomotiveScroll({
  el: document.querySelector('[data-scroll-container]'),
  smooth: true
});

ScrollTrigger.scrollerProxy("[data-scroll-container]", {
  scrollTop(value) {
    return arguments.length ? scroller.scrollTo(value, 0, 0) : scroller.scroll.instance.scroll.y;
  },
  getBoundingClientRect() {
    return {top: 0, left: 0, width: window.innerWidth, height: window.innerHeight};
  },
  pinType: document.querySelector("[data-scroll-container]").style.transform ? "transform" : "fixed"
});

ScrollTrigger.addEventListener("refresh", () => scroller.update());
ScrollTrigger.refresh();

高级技巧

图片序列擦拭

const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");

const images = [];
const imageCount = 147;
const currentFrame = { value: 0 };

for (let i = 0; i < imageCount; i++) {
  const img = new Image();
  img.src = `./frames/frame_${i.toString().padStart(4, '0')}.jpg`;
  images.push(img);
}

images[0].onload = () => {
  canvas.width = images[0].width;
  canvas.height = images[0].height;
  render();
};

function render() {
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.drawImage(images[Math.floor(currentFrame.value)], 0, 0);
}

gsap.to(currentFrame, {
  value: imageCount - 1,
  snap: "value",
  ease: "none",
  scrollTrigger: {
    trigger: canvas,
    start: "top top",
    end: "+=500%",
    scrub: true,
    pin: true
  },
  onUpdate: render
});

平滑滚动到元素

gsap.registerPlugin(ScrollToPlugin);

// 滚动到元素
gsap.to(window, {
  duration: 1,
  scrollTo: "#section2",
  ease: "power2.inOut"
});

// 带偏移量
gsap.to(window, {
  duration: 1.5,
  scrollTo: { y: "#section2", offsetY: 50 },
  ease: "expo.inOut"
});

// 水平滚动
gsap.to(".container", {
  duration: 2,
  scrollTo: { x: 1000, autoKill: true }
});

条件动画(媒体查询)

ScrollTrigger.matchMedia({
  // 桌面
  "(min-width: 800px)": function() {
    gsap.to(".box", {
      x: 500,
      scrollTrigger: {
        trigger: ".box",
        start: "top center",
        end: "bottom top",
        scrub: true
      }
    });
  },

  // 移动端
  "(max-width: 799px)": function() {
    gsap.to(".box", {
      y: 200,
      scrollTrigger: {
        trigger: ".box",
        start: "top 80%",
        scrub: 1
      }
    });
  }
});

性能最佳实践

1. 使用 will-change CSS

.animated-element {
  will-change: transform, opacity;
}

2. 限制重绘

// 好:动画化 transform/opacity(GPU 加速)
gsap.to(".box", { x: 100, opacity: 0.5 });

// 避免:动画化布局属性
// gsap.to(".box", { width: 500, height: 300 }); // 导致回流

3. 清理 ScrollTrigger

// 销毁单个触发器
const trigger = ScrollTrigger.create({ /* ... */ });
trigger.kill();

// 销毁所有触发器
ScrollTrigger.getAll().forEach(t => t.kill());

// 在 React 中,使用清理函数
useGSAP(() => {
  const tween = gsap.to(".box", { /* ... */ });

  return () => {
    tween.kill();
  };
}, []);

4. 防抖调整大小

ScrollTrigger 会自动处理,但对于自定义调整大小逻辑:

let resizeTimer;
window.addEventListener("resize", () => {
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(() => {
    ScrollTrigger.refresh();
  }, 250);
});

5. 使用 invalidateOnRefresh

对于在调整大小时变化的动态值:

gsap.to(".box", {
  x: () => window.innerWidth / 2, // 动态值
  scrollTrigger: {
    trigger: ".box",
    start: "top center",
    invalidateOnRefresh: true // 在调整大小时重新计算 x
  }
});

常见陷阱

1. 对同一元素使用多个补间动画

// 问题:第二个补间动画与第一个冲突
gsap.to('h1', { x: 100, scrollTrigger: { /* ... */ } });
gsap.to('h1', { x: 200, scrollTrigger: { /* ... */ } }); // 会跳动!

// 解决方案 1:使用 fromTo
gsap.fromTo('h1', { x: 100 }, { x: 200, scrollTrigger: { /* ... */ } });

// 解决方案 2:使用 immediateRender: false
gsap.to('h1', { x: 200, immediateRender: false, scrollTrigger: { /* ... */ } });

// 解决方案 3:将 ScrollTrigger 应用到时间线
const tl = gsap.timeline({ scrollTrigger: { /* ... */ } });
tl.to('h1', { x: 100 })
  .to('h1', { x: 200 });

2. 未对多个元素使用循环

// 错误:所有元素同时动画
gsap.to('.section', {
  y: -100,
  scrollTrigger: { trigger: '.section', scrub: true }
});

// 正确:为每个部分使用循环以拥有独立触发器
gsap.utils.toArray('.section').forEach(section => {
  gsap.to(section, {
    y: -100,
    scrollTrigger: { trigger: section, scrub: true }
  });
});

3. 忘记注册插件

import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { ScrollToPlugin } from 'gsap/ScrollToPlugin';

gsap.registerPlugin(ScrollTrigger, ScrollToPlugin); // 必须调用!

4. 使用不正确的起始/结束格式

// 错误:字符串顺序错误
start: "top top", // 正确
start: "top top", // 触发器位置在视口位置之前

// 正确格式
scrollTrigger: {
  start: "top bottom" // 触发器顶部碰到视口底部
}

Individual skills in this repo

This repo contains 7 individual skills — each has its own dedicated page.

Samueladewole/claude-skills

Create API handoff documentation for frontend developers. Use when backend work is complete and needs to be documented for frontend integration, or user says 'create handoff', 'document API', 'frontend handoff', or 'API documentation'.

Samueladewole/claude-skills

Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling

Samueladewole/claude-skills

Build accessible, responsive, and performant frontend components with design system best practices, modern CSS, and framework-agnostic patterns.

Samueladewole/claude-skills

Document frontend data needs for backend developers. Use when frontend needs to communicate API requirements to backend, or user says 'backend requirements', 'what data do I need', 'API requirements', or is describing data needs for a UI.

Samueladewole/claude-skills

Master Next.js 14+ App Router with Server Components, streaming, parallel routes, and advanced data fetching. Use when building Next.js applications, implementing SSR/SSG, or optimizing React Server Components.

Samueladewole/claude-skills

Build scalable design systems with Tailwind CSS, design tokens, component libraries, and responsive patterns. Use when creating component libraries, implementing design systems, or standardizing UI patterns.

Samueladewole/claude-skills

Analyze a short-form video (TikTok, Instagram Reel, YouTube Short, or any MP4) to extract its full transcript, understand why it performed, and generate concrete repurposing ideas. Use this skill whenever the user asks to: analyze a video, understand why something went viral, reverse-engineer a creator's content, extract a transcript from a video, figure out what makes a video perform, or get repurposing ideas from a video. Also trigger when the user says things like "why did this perform", "what's the hook here", "can I repurpose this", "analyze this TikTok/Reel", or drops a video file path or URL.

相关技能