嗨咿,我是 illumi,今天一起來用 GSAP 做這個觸發掉落的特效吧!
看著有很多變化,其實只是 物件的 y 改變而已,只用到最簡單的Tween !
首先 import 插件 ,並放入你準備的圖片:
import { useRef } from "react";
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
import { bagel1, bagel2, bagel3, bagel4 } from "@/assets/bagel";
const IMAGES = [bagel1, bagel2, bagel3, bagel4];
再來要給 gsap 一個控制的 ref
const Drop = () => {
const containerRef = useRef<HTMLDivElement>(null);
return (
<div ref={containerRef} >
</div>
);
};
export default Drop;
因為只有四張圖,想要有掉下很多的感覺,所以用 map
來增生!
const cols = IMAGES.length; // 4
const rows = 4; // 4 × 4 = 16
const box = 100; // 圖片寬高 (px)
const Drop = ({ className }: { className?: string }) => {
const containerRef = useRef<HTMLDivElement>(null);
const items = Array.from({ length: rows * cols }, (_, i) => IMAGES[i % cols]);
return (
<div ref={containerRef} className={`relative mx-auto ${className}`}>
{items.map((src, idx) => {
<img
key={idx}
src={src}
className="falling w-24 h-24"
alt={`box-${idx}`}
/>
);
})}
</div>
);
};
export default Drop;
然後寫gsap 控制
useGSAP(
() => {
const nodes = gsap.utils.toArray<HTMLImageElement>(".falling");
nodes.forEach((el, idx) => {
gsap.fromTo(
el,
{ y: -400, opacity: 0 },
{
y: 0,
opacity: 1,
ease: "bounce.out", // 模擬落地彈跳
duration: 0.5,
delay: idx * 0.05, // 依序掉落
}
);
});
},
{ scope: containerRef }
); // 使用 scope 限制動畫範圍
如果沒有寫落下位置,他會非常整整齊齊,為了營造隨機感:
const box = 100; // 圖片寬高 (px)
{items.map((src, idx) => {
// left: 隨機在容器寬度內(不超出)
const left = Math.random() * (800 - box);
// 落點 bottom: 0 ~ bottom: 20 之間
const minBottom = 0;
const maxBottom = 0;
const bottom = minBottom + Math.random() * (maxBottom - minBottom);
const rotate = Math.random() * 60 - 30;
return (
<img
key={idx}
src={src}
className="falling w-24 h-24"
style={{
width: box,
height: box,
position: "absolute",
left: Math.max(0, Math.min(left, 800 - box)),
bottom: Math.max(0, Math.min(bottom, 0)),
userSelect: "none",
transform: `rotate(${rotate}deg)`,
}}
alt={`box-${idx}`}
/>
);
})}
父層再用 onclick
或 onMouseEnter
觸發就 ok 了!
附上程式碼:
import { useRef } from "react";
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
import { bagel1, bagel2, bagel3, bagel4 } from "@/assets/bagel";
const IMAGES = [bagel1, bagel2, bagel3, bagel4];
const cols = IMAGES.length; // 4
const rows = 4; // 4 × 4 = 16
const box = 100; // 圖片寬高 (px)
const Drop = ({ className }: { className?: string }) => {
const containerRef = useRef<HTMLDivElement>(null);
const items = Array.from({ length: rows * cols }, (_, i) => IMAGES[i % cols]);
useGSAP(
() => {
const nodes = gsap.utils.toArray<HTMLImageElement>(".falling");
nodes.forEach((el, idx) => {
gsap.fromTo(
el,
{ y: -400, opacity: 0 },
{
y: 0,
opacity: 1,
ease: "bounce.out", // 模擬落地彈跳
duration: 0.5,
delay: idx * 0.05, // 依序掉落
}
);
});
},
{ scope: containerRef }
); // 使用 scope 限制動畫範圍
return (
<div ref={containerRef} className={`relative mx-auto ${className}`}>
{items.map((src, idx) => {
// left: 隨機在容器寬度內(不超出)
const left = Math.random() * (800 - box);
// 落點 bottom: 0 ~ bottom: 20 之間
const minBottom = 0;
const maxBottom = 0;
const bottom = minBottom + Math.random() * (maxBottom - minBottom);
const rotate = Math.random() * 60 - 30;
return (
<img
key={idx}
src={src}
className="falling w-24 h-24"
style={{
width: box,
height: box,
position: "absolute",
left: Math.max(0, Math.min(left, 800 - box)),
bottom: Math.max(0, Math.min(bottom, 0)),
userSelect: "none",
transform: `rotate(${rotate}deg)`,
}}
alt={`box-${idx}`}
/>
);
})}
</div>
);
};
export default Drop;