Skip to content

Commit 9a76563

Browse files
committed
refactor: landingGSAP 함수를 재사용하여 최적화
1 parent a958739 commit 9a76563

3 files changed

Lines changed: 79 additions & 102 deletions

File tree

animations/landingGSAP.ts

Lines changed: 71 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -3,131 +3,105 @@ import { ScrollTrigger } from 'gsap/ScrollTrigger';
33

44
let pluginsRegistered = false;
55

6-
const ensurePlugins = () => {
7-
if (pluginsRegistered) return;
8-
gsap.registerPlugin(ScrollTrigger);
9-
pluginsRegistered = true;
10-
};
6+
interface AnimateOptions {
7+
trigger: HTMLElement; // 스크롤 트리거 기준 요소
8+
targets: gsap.TweenTarget; // 애니메이션 대상
9+
stagger?: number; // 순차 재생 간격
10+
start?: string; // 스크롤 시작 지점
11+
yOffset?: number; // y축 이동 거리
12+
}
1113

12-
export type HeroSectionRefs = {
13-
titleEl: HTMLHeadingElement;
14-
subTextEl: HTMLDivElement;
15-
};
16-
17-
export function initHeroSectionAnimation({
18-
titleEl,
19-
subTextEl,
20-
}: HeroSectionRefs) {
21-
ensurePlugins();
14+
/* Fad Up 함수 */
15+
const animateFadeUp = ({
16+
trigger,
17+
targets,
18+
stagger = 0,
19+
start = 'top center-=50',
20+
yOffset = 24,
21+
}: AnimateOptions) => {
22+
if (!pluginsRegistered) {
23+
gsap.registerPlugin(ScrollTrigger);
24+
pluginsRegistered = true;
25+
}
2226

2327
const ctx = gsap.context(() => {
2428
gsap.fromTo(
25-
subTextEl,
26-
{ opacity: 0, y: 40 },
29+
targets,
30+
{ opacity: 0, y: yOffset },
2731
{
2832
opacity: 1,
2933
y: 0,
30-
duration: 0.8,
34+
duration: 0.6,
3135
ease: 'power2.out',
36+
stagger: stagger,
3237
scrollTrigger: {
33-
trigger: titleEl,
34-
start: 'top center-=150',
38+
trigger: trigger,
39+
start: start,
3540
toggleActions: 'play none none reverse',
3641
// markers: true,
3742
},
3843
}
3944
);
4045
});
4146

42-
return () => ctx.revert(); // ✅ cleanup 반환
43-
}
44-
export type FeatureListRefs = {
45-
titleEl: HTMLHeadingElement; // "개발자 성장을 위한"
46-
subTextEl: HTMLDivElement; // 서브 텍스트 wrapper
47-
cardsContainerEl: HTMLDivElement; // grid wrapper
47+
return () => ctx.revert();
4848
};
4949

50-
export function initFeatureListAnimation({
50+
/* --- 1. Hero Section --- */
51+
export type HeroSectionRefs = {
52+
titleEl: HTMLHeadingElement;
53+
subTextEl: HTMLDivElement;
54+
};
55+
56+
export const initHeroSectionAnimation = ({
5157
titleEl,
5258
subTextEl,
53-
cardsContainerEl,
54-
}: FeatureListRefs) {
55-
ensurePlugins();
56-
57-
const ctx = gsap.context(() => {
58-
// 카드들: grid 안의 직계 자식(FeatureCard wrapper)을 전부 잡음
59-
const cards = Array.from(cardsContainerEl.children) as HTMLElement[];
60-
61-
// 초기 상태 (깜빡임 방지)
62-
gsap.set(subTextEl, { opacity: 0, y: 24 });
63-
gsap.set(cards, { opacity: 0, y: 28 });
59+
}: HeroSectionRefs) => {
60+
return animateFadeUp({
61+
trigger: titleEl,
62+
targets: subTextEl,
63+
start: 'top center-=150',
64+
yOffset: 40,
65+
});
66+
};
6467

65-
const tl = gsap.timeline({
66-
scrollTrigger: {
67-
trigger: titleEl,
68-
start: 'top center-=50', // 타이틀 끝이 화면 중간보다 조금 아래에 걸리면
69-
toggleActions: 'play none none reverse',
70-
// markers: true,
71-
},
72-
});
68+
/* --- 2. Feature List Section --- */
69+
export type FeatureListRefs = {
70+
titleEl: HTMLHeadingElement;
71+
subTextEl: HTMLDivElement;
72+
cardsContainerEl: HTMLDivElement;
73+
};
7374

74-
// 1) 서브 텍스트 먼저
75-
tl.to(subTextEl, {
76-
opacity: 1,
77-
y: 0,
78-
duration: 0.6,
79-
ease: 'power2.out',
80-
});
75+
export const initFeatureListAnimation = ({
76+
titleEl,
77+
subTextEl,
78+
cardsContainerEl,
79+
}: FeatureListRefs) => {
80+
const cards = Array.from(cardsContainerEl.children);
8181

82-
// 2) 카드들 stagger로 아래→위 쓱~
83-
tl.to(
84-
cards,
85-
{
86-
opacity: 1,
87-
y: 0,
88-
duration: 0.6,
89-
ease: 'power2.out',
90-
stagger: 0.12,
91-
},
92-
'-=0.15' // 서브텍스트 끝나기 조금 전에 겹쳐서 시작
93-
);
82+
return animateFadeUp({
83+
trigger: titleEl,
84+
targets: [subTextEl, ...cards],
85+
stagger: 0.12,
9486
});
87+
};
9588

96-
return () => ctx.revert();
97-
}
89+
/* --- 3. Landing Preview Section --- */
9890
export type LandingPreviewRefs = {
99-
rootEl: HTMLElement; // 섹션 전체
100-
titleEl: HTMLHeadingElement; // "체계적인 학습으로" h1
101-
contentContainerEl: HTMLDivElement; // FeatureContent 감싸는 div
91+
rootEl: HTMLElement;
92+
titleEl: HTMLHeadingElement;
93+
contentContainerEl: HTMLDivElement;
10294
};
10395

104-
export function initLandingPreviewAnimation({
105-
rootEl,
96+
export const initLandingPreviewAnimation = ({
10697
titleEl,
10798
contentContainerEl,
108-
}: LandingPreviewRefs) {
109-
ensurePlugins();
110-
111-
const ctx = gsap.context(() => {
112-
const items = contentContainerEl.querySelectorAll<HTMLElement>('.fc-item');
99+
}: LandingPreviewRefs) => {
100+
const items = contentContainerEl.querySelectorAll('#fc-item');
113101

114-
// ✅ 레이아웃 유지한 채로 숨김 (grid 비율 절대 안 깨짐)
115-
gsap.set(items, { opacity: 0, y: 24 });
116-
117-
gsap.to(items, {
118-
opacity: 1,
119-
y: 0,
120-
duration: 0.6,
121-
ease: 'power2.out',
122-
stagger: 0.12,
123-
scrollTrigger: {
124-
trigger: titleEl,
125-
start: 'top center-=50', // 필요하면 75%~85%로 튜닝
126-
toggleActions: 'play none none reverse',
127-
// markers: true,
128-
},
129-
});
130-
}, rootEl);
131-
132-
return () => ctx.revert();
133-
}
102+
return animateFadeUp({
103+
trigger: titleEl,
104+
targets: items,
105+
stagger: 0.2,
106+
});
107+
};

components/landing/FeatureContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const FeatureContent = () => {
2323
return (
2424
<ul className="space-y-6">
2525
{ITEMS.map((item) => (
26-
<li key={item.title} className="fc-item">
26+
<li key={item.title} id="fc-item">
2727
<div className="flex gap-3">
2828
<FiCheckCircle className="text-primary text-2xl" />
2929
<p className="text-xl font-medium">{item.title}</p>

components/landing/LandingPreview.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@ const LandingPreview = () => {
2828
체계적인 학습으로
2929
</h1>
3030
<h1 className="text-primary text-4xl font-bold">더 빠른 성장</h1>
31-
<div className="mx-20 mt-10 grid max-w-7xl grid-cols-1 items-start gap-10 lg:grid-cols-2 lg:gap-2">
32-
{/* 왼쪽: 400 밑으로 못 내려가게 */}
33-
<div ref={contentRef} className="flex min-w-[355px] justify-center">
31+
<div
32+
className="mx-20 mt-10 grid max-w-7xl grid-cols-1 items-start gap-10 lg:grid-cols-2 lg:gap-2"
33+
ref={contentRef}
34+
>
35+
36+
<div className="flex min-w-[355px] justify-center">
3437
<FeatureContent />
3538
</div>
3639

37-
{/* 오른쪽: 이미지 그대로 */}
3840
<div className="flex justify-center">
3941
<Image
42+
id="fc-item"
4043
src="/PreviewImage.png"
4144
alt="DevFlow 미리보기"
4245
width={720}

0 commit comments

Comments
 (0)