简介
gsap 高性能的 JavaScript 动画库,在现代网页设计和开发中运用。
安装
npm install gsap
React 框架中使用
可以考滤使用 react-gsap-enhancer
库,或者 @gasp/react
。
类组件使用 react-gsap-enhancer
高阶组件,函数组件使用 @gasp/react
自定义 Hook。
npm install react-gsap-enhancer
#or
yarn add react-gsap-enhancer
ScrollTrigger
ScrollTrigger 用于在滚动时触发动画。如滚动到指定位置时触发动画、滚动到指定位置时结束动画、滚动到指定位置时重复动画等。
基本使用
import React from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(useGSAP, ScrollTrigger);
const TextAnimation = () => {
useGSAP(() => {
gsap.fromTo(
".gasp-text",
{
y: 100, // 初始位置
},
{
scrollTrigger: {
trigger: ".gasp-text", // 触发动画的元素
start: "top bottom-=100", // 动画开始的位置
toggleActions: "play none none reverse", // 动画触发时的行为
markers: true, // 显示标记
},
duration: 1, // 动画持续时间
y: 0, // 动画结束时的位置
}
);
});
return (
<div>
<div className="gasp-text">hello world</div>
</div>
);
};
常用属性
trigger
: 触发动画的元素start
: 动画开始的位置,可以是相对于触发元素的位置,也可以是相对于视口的位置end
: 动画结束的位置,可以是相对于触发元素的位置,也可以是相对于视口的位置toggleActions
: 动画触发时的行为,可以是 "play"(播放)、"pause"(暂停)、"resume"(恢复)、"reset"(重置)、"restart"(重新开始)。 按顺序为 onEnter、onLeave、onEnterBack 和 onLeaveBack,默认值 play none none none。进入 离开 返回进入 返回离开markers
: 是否显示标记,用于调试
页面视差滚动
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(useGSAP, ScrollTrigger);
export default function GaspScrollDemo() {
const sectionsRef = useRef([]);
const titlesRef = useRef([]);
const descsRef = useRef([]);
// 滚动触发的动画
useGSAP(() => {
sectionsRef.current.forEach((section, index) => {
// 创建时间线
const tl = gsap.timeline({
scrollTrigger: {
trigger: section,
start: "top top",
end: "bottom top",
pin: true,
pinSpacing: true,
scrub: false,
},
});
// 随机方向数组
const directions = [
{ x: -100, y: 0 },
{ x: 100, y: 0 },
{ x: 0, y: -100 },
{ x: 0, y: 100 },
{ x: -100, y: -100 },
{ x: 100, y: 100 },
{ x: -100, y: 100 },
];
// 进入动画
tl.fromTo(
[titlesRef.current[index], descsRef.current[index]],
{
...directions[index],
opacity: 0,
scale: 0.5,
rotation: Math.random() * 30 - 15,
},
{
x: 0,
y: 0,
opacity: 1,
scale: 1,
rotation: 0,
duration: 1,
ease: "power2.out",
}
);
// 停留动画
tl.to(
[titlesRef.current[index], descsRef.current[index]],
{
scale: 1.05,
duration: 0.5,
ease: "power1.inOut",
},
"+=0.2"
).to([titlesRef.current[index], descsRef.current[index]], {
scale: 1,
duration: 0.5,
ease: "power1.inOut",
});
// 离开动画
tl.to(
[titlesRef.current[index], descsRef.current[index]],
{
y: -100,
opacity: 0,
scale: 0.8,
rotation: -Math.random() * 30 + 15,
duration: 1,
ease: "power2.in",
},
"+=0.5"
);
});
}, []);
const sections = [
{
color: "bg-red-500",
title: "Red Section",
desc: "A vibrant red section with dynamic animations",
},
{
color: "bg-orange-500",
title: "Orange Section",
desc: "Warm orange tones with smooth transitions",
},
{
color: "bg-yellow-500",
title: "Yellow Section",
desc: "Bright yellow energy with playful movements",
},
{
color: "bg-green-500",
title: "Green Section",
desc: "Fresh green space with natural flow",
},
{
color: "bg-blue-500",
title: "Blue Section",
desc: "Calm blue atmosphere with gentle animations",
},
{
color: "bg-indigo-500",
title: "Indigo Section",
desc: "Deep indigo depth with smooth transitions",
},
{
color: "bg-purple-500",
title: "Purple Section",
desc: "Rich purple elegance with dynamic effects",
},
];
return (
<div className="relative">
{sections.map((section, index) => (
<div
key={index}
ref={(el) => (sectionsRef.current[index] = el)}
className={`${section.color} h-screen w-full relative overflow-hidden flex items-center justify-center`}
>
<div className="text-center z-10 px-4">
<h2
ref={(el) => (titlesRef.current[index] = el)}
className="text-6xl font-bold text-white mb-6"
>
{section.title}
</h2>
<p
ref={(el) => (descsRef.current[index] = el)}
className="text-xl text-white/80 max-w-2xl mx-auto"
>
{section.desc}
</p>
</div>
</div>
))}
</div>
);
}
ScrollSmoother
ScrollSmoother 为基于 ScrollTrigger 的页面添加了垂直平滑滚动效果。
基本使用
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollSmoother } from "gsap/ScrollSmoother";
import { ScrollToPlugin } from "gsap/ScrollToPlugin";
gsap.registerPlugin(useGSAP, ScrollTrigger, ScrollSmoother, ScrollToPlugin);
export default function GaspScrollDemo() {
const [activeSection, setActiveSection] = useState("box1");
const sectionsRef = useRef([]);
const titlesRef = useRef([]);
const descsRef = useRef([]);
// 创建动画时间线的函数
const createAnimation = (index) => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: sectionsRef.current[index],
start: "top center",
end: "bottom center",
toggleActions: "play none none reverse",
},
});
tl.fromTo(
titlesRef.current[index],
{
y: 100,
opacity: 0,
},
{
y: 0,
opacity: 1,
duration: 1,
ease: "power2.out",
}
);
tl.fromTo(
descsRef.current[index],
{
y: 50,
opacity: 0,
},
{
y: 0,
opacity: 1,
duration: 1,
ease: "power2.out",
},
"-=0.5"
);
return tl;
};
useGSAP(() => {
// 创建平滑滚动
const smoother = ScrollSmoother.create({
wrapper: "#smooth-wrapper",
content: "#smooth-content",
smooth: 1.5,
effects: true,
});
// 为每个部分创建 ScrollTrigger
sectionsRef.current.forEach((section, index) => {
ScrollTrigger.create({
trigger: section,
start: "top center",
end: "bottom center",
onEnter: () => {
setActiveSection(`box${index + 1}`);
// 如果是第一屏,手动触发动画
if (index === 0) {
createAnimation(0);
}
},
onEnterBack: () => {
setActiveSection(`box${index + 1}`);
// 如果是第一屏,手动触发动画
if (index === 0) {
createAnimation(0);
}
},
});
// 创建动画
createAnimation(index);
});
// 初始加载时触发第一屏动画
createAnimation(0);
}, []);
const goToBox = (id) => {
gsap.to(window, {
duration: 1,
scrollTo: {
y: `#${id}`,
offsetY: 0,
autoKill: false,
},
ease: "power2.inOut",
});
};
const buttons = [
{ id: "box1", color: "bg-red-500", activeColor: "bg-red-900" },
{ id: "box2", color: "bg-orange-500", activeColor: "bg-orange-900" },
{ id: "box3", color: "bg-yellow-500", activeColor: "bg-yellow-900" },
{ id: "box4", color: "bg-green-500", activeColor: "bg-green-900" },
{ id: "box5", color: "bg-blue-500", activeColor: "bg-blue-900" },
{ id: "box6", color: "bg-indigo-500", activeColor: "bg-indigo-900" },
];
const sections = [
{
id: "box1",
title: "Section 1",
desc: "Welcome to Section 1",
},
{
id: "box2",
title: "Section 2",
desc: "Welcome to Section 2",
},
{
id: "box3",
title: "Section 3",
desc: "Welcome to Section 3",
},
{
id: "box4",
title: "Section 4",
desc: "Welcome to Section 4",
},
{
id: "box5",
title: "Section 5",
desc: "Welcome to Section 5",
},
{
id: "box6",
title: "Section 6",
desc: "Welcome to Section 6",
},
];
return (
<div id="smooth-wrapper" className="h-screen overflow-hidden">
<div className="fixed top-[30%] right-[10px] flex-col flex items-center gap-4 z-50">
{buttons.map((button) => (
<button
key={button.id}
className={`w-10 h-10 transition-all duration-300 border-2 border-black ${
activeSection === button.id ? button.activeColor : button.color
}`}
onClick={() => goToBox(button.id)}
/>
))}
</div>
<div id="smooth-content" className="relative">
{sections.map((section, index) => (
<div
key={section.id}
ref={(el) => (sectionsRef.current[index] = el)}
id={section.id}
className={`${buttons[index].color} h-screen w-full relative flex items-center justify-center`}
>
<div className="text-center z-10 px-4 max-w-3xl">
<h2
ref={(el) => (titlesRef.current[index] = el)}
className="text-6xl font-bold text-white mb-6"
>
{section.title}
</h2>
<p
ref={(el) => (descsRef.current[index] = el)}
className="text-xl text-white/80"
>
{section.desc}
</p>
</div>
</div>
))}
</div>
</div>
);
}
ScrollToPlugin
滚动到指定位置
基本使用
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollToPlugin } from "gsap/ScrollToPlugin";
gsap.registerPlugin(useGSAP, ScrollTrigger, ScrollToPlugin);
export default function GaspScrollDemo() {
const [activeSection, setActiveSection] = useState("box1");
const sectionsRef = useRef([]);
useGSAP(() => {
// 为每个部分创建 ScrollTrigger
sectionsRef.current.forEach((section, index) => {
ScrollTrigger.create({
trigger: section,
start: "top center",
end: "bottom center",
onEnter: () => setActiveSection(`box${index + 1}`),
onEnterBack: () => setActiveSection(`box${index + 1}`),
});
});
}, []);
const goToBox = (id) => {
gsap.to(window, {
duration: 1,
scrollTo: {
y: `#${id}`,
offsetY: 0,
autoKill: false,
},
ease: "power2.inOut",
});
};
const buttons = [
{ id: "box1", color: "bg-red-500", activeColor: "bg-red-900" },
{ id: "box2", color: "bg-orange-500", activeColor: "bg-orange-900" },
{ id: "box3", color: "bg-yellow-500", activeColor: "bg-yellow-900" },
{ id: "box4", color: "bg-green-500", activeColor: "bg-green-900" },
{ id: "box5", color: "bg-blue-500", activeColor: "bg-blue-900" },
{ id: "box6", color: "bg-indigo-500", activeColor: "bg-indigo-900" },
];
return (
<div className="box">
<div className="fixed top-[30%] right-[10px] flex-col flex items-center gap-4">
{buttons.map((button) => (
<button
key={button.id}
className={`w-10 h-10 transition-all duration-300 border-2 border-black ${
activeSection === button.id ? button.activeColor : button.color
}`}
onClick={() => goToBox(button.id)}
/>
))}
</div>
<div
ref={(el) => (sectionsRef.current[0] = el)}
id="box1"
className="bg-red-500 h-screen w-full"
/>
<div
ref={(el) => (sectionsRef.current[1] = el)}
id="box2"
className="bg-orange-500 h-screen w-full"
/>
<div
ref={(el) => (sectionsRef.current[2] = el)}
id="box3"
className="bg-yellow-500 h-screen w-full"
/>
<div
ref={(el) => (sectionsRef.current[3] = el)}
id="box4"
className="bg-green-500 h-screen w-full"
/>
<div
ref={(el) => (sectionsRef.current[4] = el)}
id="box5"
className="bg-blue-500 h-screen w-full"
/>
<div
ref={(el) => (sectionsRef.current[5] = el)}
id="box6"
className="bg-indigo-500 h-screen w-full"
/>
</div>
);
}