@@ -3,131 +3,105 @@ import { ScrollTrigger } from 'gsap/ScrollTrigger';
33
44let 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 --- */
9890export 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+ } ;
0 commit comments