滚动联动
ui
0%
src
TSX
// npm install gsap
"use client";
import { useLayoutEffect, useRef, useState } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const SATELLITES = [0, 60, 120, 180, 240, 300];
export default function Page() {
const scope = useRef<HTMLDivElement>(null);
const [pct, setPct] = useState(0);
useLayoutEffect(() => {
const ctx = gsap.context(() => {
/* 一条时间线挂一个 ScrollTrigger,所有元素共享同一份滚动进度 */
const tl = gsap.timeline({
defaults: { ease: "none", duration: 1 },
scrollTrigger: {
trigger: scope.current,
start: "top 90%",
end: "bottom 10%",
scrub: 0.5,
onUpdate: (self) => setPct(Math.round(self.progress * 100)),
},
});
tl.fromTo(
".scrub-core",
{ rotation: 0, scale: 1, borderRadius: "0%" },
{ rotation: 360, scale: 1.5, borderRadius: "50%" },
0
)
.fromTo(
".scrub-track",
{ rotation: 0, scale: 0.8, opacity: 0.25 },
{ rotation: -180, scale: 1.06, opacity: 0.7 },
0
)
.fromTo(".scrub-orbit", { rotation: 0, scale: 0.65 }, { rotation: 270, scale: 1.06 }, 0)
/* stagger 在 scrub 下变成「随滚动逐个亮起」 */
.fromTo(".scrub-sat", { opacity: 0 }, { opacity: 1, duration: 0.8, stagger: 0.12 }, 0)
.fromTo(".scrub-bar", { scaleX: 0 }, { scaleX: 1 }, 0);
}, scope);
return () => ctx.revert();
}, []);
return (
<div ref={scope} className="flex min-h-screen flex-col items-center justify-center gap-16">
<div className="relative flex size-64 items-center justify-center">
<div className="scrub-track absolute inset-0 rounded-full border border-dashed border-fuchsia-500/35" />
<div className="scrub-orbit absolute inset-0">
{SATELLITES.map((a) => (
<div key={a} className="absolute inset-0" style={{ rotate: `${a}deg` }}>
<span className="scrub-sat absolute left-1/2 top-0 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-fuchsia-500" />
</div>
))}
</div>
<div className="scrub-core size-24 bg-gradient-to-br from-blue-600 via-fuchsia-600 to-pink-500" />
</div>
<div className="flex w-96 items-center gap-5">
<div className="h-1 flex-1 bg-fuchsia-500/15">
<div className="scrub-bar h-full w-full origin-left bg-gradient-to-r from-blue-600 via-fuchsia-600 to-pink-500" />
</div>
<span className="w-14 text-right font-mono tabular-nums text-white/50">{pct}%</span>
</div>
</div>
);
}