简介
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
Flip
Flip
是 gsap 提供的一个插件,用于实现翻转动画。
3d 卡片翻转动画
import { useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { Flip } from "gsap/Flip";
import "../styles/flip.css";
gsap.registerPlugin(useGSAP, Flip);
export default function GaspFlipDemo() {
const [isFlipped, setIsFlipped] = useState(false);
const containerRef = useRef(null);
const cardRef = useRef(null);
useGSAP(() => {
const card = cardRef.current;
if (!card) return;
const flipState = Flip.getState(card);
if (isFlipped) {
card.style.transform = "rotateY(180deg)";
} else {
card.style.transform = "rotateY(0deg)";
}
Flip.from(flipState, {
duration: 0.8,
ease: "power2.inOut",
onComplete: () => {
// 动画完成后的回调
},
});
}, [isFlipped]);
return (
<div className="w-full h-[500px] flex items-center justify-center">
<div
ref={containerRef}
className="perspective-1000"
onClick={() => setIsFlipped(!isFlipped)}
>
<div
ref={cardRef}
className="w-[300px] h-[400px] relative cursor-pointer transition-transform duration-300"
style={{ transformStyle: "preserve-3d" }}
>
{/* 卡片正面 */}
<div className="absolute w-full h-full bg-blue-500 rounded-lg flex items-center justify-center text-white text-2xl backface-hidden">
Front
</div>
{/* 卡片背面 */}
<div
className="absolute w-full h-full bg-red-500 rounded-lg flex items-center justify-center text-white text-2xl backface-hidden"
style={{ transform: "rotateY(180deg)" }}
>
Back
</div>
</div>
</div>
</div>
);
}
.perspective-1000 {
perspective: 1000px;
}
.backface-hidden {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
拖拽排序
import { useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { Flip } from "gsap/Flip";
gsap.registerPlugin(useGSAP, Flip);
export default function GaspFlipDemo() {
const [items, setItems] = useState([
{ id: 1, color: "bg-blue-500" },
{ id: 2, color: "bg-green-500" },
{ id: 3, color: "bg-red-500" },
{ id: 4, color: "bg-yellow-500" },
]);
const [draggedItem, setDraggedItem] = useState(null);
const containerRef = useRef(null);
const handleDragStart = (e, item) => {
setDraggedItem(item);
const target = e.target;
// 创建拖拽时的动画效果
gsap.to(target, {
scale: 1.1,
rotation: 5,
duration: 0.2,
boxShadow: "0 10px 20px rgba(0,0,0,0.2)",
zIndex: 100,
});
};
const handleDragOver = (e, targetItem) => {
e.preventDefault();
if (!draggedItem || draggedItem.id === targetItem.id) return;
// 为放置目标添加悬停效果
gsap.to(e.target, {
scale: 1.05,
duration: 0.2,
boxShadow: "0 5px 15px rgba(0,0,0,0.1)",
});
};
const handleDragLeave = (e) => {
// 移除悬停效果
gsap.to(e.target, {
scale: 1,
duration: 0.2,
boxShadow: "none",
});
};
const handleDrop = (e, targetItem) => {
e.preventDefault();
if (!draggedItem) return;
const draggedIndex = items.findIndex((item) => item.id === draggedItem.id);
const targetIndex = items.findIndex((item) => item.id === targetItem.id);
// 获取所有元素的当前状态
const state = Flip.getState(".item", {
props: "backgroundColor,boxShadow",
simple: true,
});
// 更新状态
const newItems = [...items];
[newItems[draggedIndex], newItems[targetIndex]] = [
newItems[targetIndex],
newItems[draggedIndex],
];
setItems(newItems);
// 执行动画
Flip.from(state, {
duration: 0.6,
ease: "power2.inOut",
onComplete: () => {
// 重置所有元素的样式
gsap.to(".item", {
scale: 1,
rotation: 0,
boxShadow: "none",
duration: 0.3,
clearProps: "all",
});
setDraggedItem(null);
},
});
};
return (
<div className="w-full h-[500px] flex items-center justify-center">
<div
ref={containerRef}
className="grid grid-cols-2 gap-4 p-4 bg-gray-100 rounded-lg"
>
{items.map((item) => (
<div
key={item.id}
className={`item w-32 h-32 ${item.color} rounded-lg cursor-move flex items-center justify-center text-white text-xl transition-all`}
draggable
onDragStart={(e) => handleDragStart(e, item)}
onDragOver={(e) => handleDragOver(e, item)}
onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e, item)}
>
{item.id}
</div>
))}
</div>
</div>
);
}
Draggable
拖拽效果
import { useRef, useState, useEffect } from "react";
import { gsap } from "gsap";
import { Draggable } from "gsap/Draggable";
gsap.registerPlugin(Draggable);
export default function GaspFlipDemo() {
const boxRef = useRef(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
if (!boxRef.current) return;
// 创建可拖拽实例
const draggable = Draggable.create(boxRef.current, {
type: "x,y",
onDrag: function () {
// 更新位置状态
setPosition({
x: this.x,
y: this.y,
});
},
onDragStart: function () {
// 拖拽开始时的动画
gsap.to(this.target, {
scale: 1.1,
duration: 0.2,
});
},
onDragEnd: function () {
// 拖拽结束时的动画
gsap.to(this.target, {
scale: 1,
duration: 0.2,
});
},
});
// 清理函数
return () => {
draggable[0].kill();
};
}, []);
return (
<div
ref={boxRef}
className="absolute w-[200px] h-[200px] bg-blue-500 rounded-lg cursor-move flex items-center justify-center text-white"
style={{
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
}}
>
Drag Me
<div className="text-sm mt-1">
X: {Math.round(position.x)} Y: {Math.round(position.y)}
</div>
</div>
);
}
滑动验证
X 轴方向滑动特效
import { useRef, useState, useEffect } from "react";
import { gsap } from "gsap";
import { Draggable } from "gsap/Draggable";
gsap.registerPlugin(Draggable);
export default function GaspFlipDemo() {
const sliderRef = useRef(null);
const trackRef = useRef(null);
const [isVerified, setIsVerified] = useState(false);
const [sliderPosition, setSliderPosition] = useState(0);
const sliderWidth = 40; // 滑块宽度
const trackWidth = 280; // 轨道实际可滑动宽度
useEffect(() => {
if (!sliderRef.current || !trackRef.current) return;
const draggable = Draggable.create(sliderRef.current, {
type: "x",
bounds: {
minX: 0,
maxX: trackWidth - sliderWidth, // 限制最大滑动距离,考虑滑块宽度
},
onDrag: function () {
const progress = (this.x / (trackWidth - sliderWidth)) * 100;
setSliderPosition(this.x);
// 更新滑块背景,使用伪元素实现圆角
gsap.to(trackRef.current, {
background: `linear-gradient(to right, #4CAF50 ${progress}%, #e0e0e0 ${progress}%)`,
duration: 0.1,
});
},
onDragEnd: function () {
// 验证是否滑动到终点
if (this.x >= trackWidth - sliderWidth - 5) {
setIsVerified(true);
gsap.to(trackRef.current, {
background: "#4CAF50",
duration: 0.3,
});
} else {
// 未完成验证,滑块回弹
gsap.to(sliderRef.current, {
x: 0,
duration: 0.3,
ease: "power2.out",
});
gsap.to(trackRef.current, {
background: "#e0e0e0",
duration: 0.3,
});
setSliderPosition(0);
}
},
});
return () => {
draggable[0].kill();
};
}, []);
return (
<div className="w-full h-[100vh] flex items-center justify-center">
<div className="w-[320px] p-4 bg-white rounded-lg shadow-lg">
<h3 className="text-lg font-medium mb-4">滑动验证</h3>
{/* 滑块轨道 */}
<div
ref={trackRef}
className="relative h-[40px] bg-[#e0e0e0] rounded-full overflow-hidden"
style={{ width: `${trackWidth}px` }}
>
{/* 滑块 */}
<div
ref={sliderRef}
className="absolute w-[40px] h-[40px] bg-white rounded-full shadow-md cursor-move flex items-center justify-center"
style={{
left: 0,
top: 0,
transform: `translateX(${sliderPosition}px)`,
}}
>
<svg
className="w-5 h-5 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</div>
</div>
{/* 验证状态 */}
<div className="mt-4 text-center">
{isVerified ? (
<span className="text-green-500">验证成功!</span>
) : (
<span className="text-gray-500">向右滑动完成验证</span>
)}
</div>
</div>
</div>
);
}
InertiaPlugin
惯性插件,用于实现惯性滑动效果
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { InertiaPlugin } from "gsap/InertiaPlugin";
import { Draggable } from "gsap/Draggable";
gsap.registerPlugin(InertiaPlugin, Draggable);
export default function GsapUiDemo() {
const cardRef = useRef(null);
const containerRef = useRef(null);
useGSAP(() => {
if (!cardRef.current || !containerRef.current) return;
// 创建可拖动实例
Draggable.create(cardRef.current, {
type: "x,y",
inertia: true,
bounds: containerRef.current,
onDragStart: function () {
gsap.set(this.target, { zIndex: 100 });
},
onDragEnd: function () {
gsap.set(this.target, { zIndex: 1 });
},
});
}, []);
return (
<div
ref={containerRef}
className="w-full h-[100vh] overflow-hidden relative bg-gray-100"
>
<div
ref={cardRef}
className="absolute w-48 h-48 bg-white rounded-lg shadow-lg cursor-move"
style={{
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
}}
>
<div className="p-4">
<h3 className="text-xl font-bold mb-2">可拖动卡片</h3>
<p className="text-gray-600">尝试拖动我,感受惯性效果</p>
</div>
</div>
</div>
);
}
Observer
观察者插件,用于监听元素状态变化,如进入视口、离开视口等
import { useEffect } from "react";
import { gsap } from "gsap";
import { Observer } from "gsap/Observer";
export default function GsapObserverDemo() {
const boxRef = useRef(null);
useEffect(() => {
if (!boxRef.current) return;
const observer = Observer.create({
target: boxRef.current,
type: "visibility",
callback: (self) => {
if (self.isIntersecting) {
// 元素进入视口
gsap.to(self.target, {
scale: 1.2,
duration: 0.3,
});
} else {
// 元素离开视口
gsap.to(self.target, {
scale: 1,
duration: 0.3,
});
}
},
});
return () => {
observer.kill();
};
}, []);
return (
<div className="w-full h-[100vh] flex items-center justify-center">
<div
ref={boxRef}
className="w-[100px] h-[100px] bg-red-500 rounded-full"
></div>
</div>
);
}
原文链接:https://code.ifrontend.net/archives/340,转载请注明出处。
评论0