@hua-labs/motion-core 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +16 -19
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/core/MotionEngine.d.ts +111 -0
  4. package/dist/core/MotionEngine.d.ts.map +1 -0
  5. package/dist/core/MotionEngine.js +206 -0
  6. package/dist/core/MotionEngine.js.map +1 -0
  7. package/dist/core/PerformanceOptimizer.d.ts +124 -0
  8. package/dist/core/PerformanceOptimizer.d.ts.map +1 -0
  9. package/dist/core/PerformanceOptimizer.js +334 -0
  10. package/dist/core/PerformanceOptimizer.js.map +1 -0
  11. package/dist/core/TransitionEffects.d.ts +76 -0
  12. package/dist/core/TransitionEffects.d.ts.map +1 -0
  13. package/dist/core/TransitionEffects.js +321 -0
  14. package/dist/core/TransitionEffects.js.map +1 -0
  15. package/dist/hooks/useBounceIn.d.ts +11 -2
  16. package/dist/hooks/useBounceIn.d.ts.map +1 -1
  17. package/dist/hooks/useBounceIn.js +48 -114
  18. package/dist/hooks/useBounceIn.js.map +1 -1
  19. package/dist/hooks/useClickToggle.d.ts +28 -13
  20. package/dist/hooks/useClickToggle.d.ts.map +1 -1
  21. package/dist/hooks/useClickToggle.js +125 -90
  22. package/dist/hooks/useClickToggle.js.map +1 -1
  23. package/dist/hooks/useFadeIn.d.ts +0 -14
  24. package/dist/hooks/useFadeIn.d.ts.map +1 -1
  25. package/dist/hooks/useFadeIn.js +17 -43
  26. package/dist/hooks/useFadeIn.js.map +1 -1
  27. package/dist/hooks/useFocusToggle.d.ts +28 -13
  28. package/dist/hooks/useFocusToggle.d.ts.map +1 -1
  29. package/dist/hooks/useFocusToggle.js +125 -87
  30. package/dist/hooks/useFocusToggle.js.map +1 -1
  31. package/dist/hooks/useGesture.d.ts +45 -0
  32. package/dist/hooks/useGesture.d.ts.map +1 -0
  33. package/dist/hooks/useGesture.js +274 -0
  34. package/dist/hooks/useGesture.js.map +1 -0
  35. package/dist/hooks/useGestureMotion.d.ts +26 -0
  36. package/dist/hooks/useGestureMotion.d.ts.map +1 -0
  37. package/dist/hooks/useGestureMotion.js +167 -0
  38. package/dist/hooks/useGestureMotion.js.map +1 -0
  39. package/dist/hooks/useGradient.d.ts +10 -21
  40. package/dist/hooks/useGradient.d.ts.map +1 -1
  41. package/dist/hooks/useGradient.js +70 -127
  42. package/dist/hooks/useGradient.js.map +1 -1
  43. package/dist/hooks/useHoverMotion.d.ts +4 -14
  44. package/dist/hooks/useHoverMotion.d.ts.map +1 -1
  45. package/dist/hooks/useHoverMotion.js +31 -82
  46. package/dist/hooks/useHoverMotion.js.map +1 -1
  47. package/dist/hooks/useMotionState.d.ts +27 -24
  48. package/dist/hooks/useMotionState.d.ts.map +1 -1
  49. package/dist/hooks/useMotionState.js +186 -103
  50. package/dist/hooks/useMotionState.js.map +1 -1
  51. package/dist/hooks/usePageMotions.d.ts +17 -0
  52. package/dist/hooks/usePageMotions.d.ts.map +1 -0
  53. package/dist/hooks/usePageMotions.js +352 -0
  54. package/dist/hooks/usePageMotions.js.map +1 -0
  55. package/dist/hooks/usePulse.d.ts +8 -1
  56. package/dist/hooks/usePulse.d.ts.map +1 -1
  57. package/dist/hooks/usePulse.js +75 -101
  58. package/dist/hooks/usePulse.js.map +1 -1
  59. package/dist/hooks/useRepeat.d.ts +17 -22
  60. package/dist/hooks/useRepeat.d.ts.map +1 -1
  61. package/dist/hooks/useRepeat.js +48 -162
  62. package/dist/hooks/useRepeat.js.map +1 -1
  63. package/dist/hooks/useScaleIn.d.ts +12 -2
  64. package/dist/hooks/useScaleIn.d.ts.map +1 -1
  65. package/dist/hooks/useScaleIn.js +46 -85
  66. package/dist/hooks/useScaleIn.js.map +1 -1
  67. package/dist/hooks/useScrollProgress.d.ts +8 -18
  68. package/dist/hooks/useScrollProgress.d.ts.map +1 -1
  69. package/dist/hooks/useScrollProgress.js +28 -130
  70. package/dist/hooks/useScrollProgress.js.map +1 -1
  71. package/dist/hooks/useScrollReveal.d.ts +12 -15
  72. package/dist/hooks/useScrollReveal.d.ts.map +1 -1
  73. package/dist/hooks/useScrollReveal.js +93 -72
  74. package/dist/hooks/useScrollReveal.js.map +1 -1
  75. package/dist/hooks/useSimplePageMotion.d.ts +29 -0
  76. package/dist/hooks/useSimplePageMotion.d.ts.map +1 -0
  77. package/dist/hooks/useSimplePageMotion.js +145 -0
  78. package/dist/hooks/useSimplePageMotion.js.map +1 -0
  79. package/dist/hooks/useSlideLeft.d.ts +12 -2
  80. package/dist/hooks/useSlideLeft.d.ts.map +1 -1
  81. package/dist/hooks/useSlideLeft.js +46 -85
  82. package/dist/hooks/useSlideLeft.js.map +1 -1
  83. package/dist/hooks/useSlideRight.d.ts +12 -2
  84. package/dist/hooks/useSlideRight.d.ts.map +1 -1
  85. package/dist/hooks/useSlideRight.js +46 -85
  86. package/dist/hooks/useSlideRight.js.map +1 -1
  87. package/dist/hooks/useSlideUp.d.ts.map +1 -1
  88. package/dist/hooks/useSlideUp.js +40 -29
  89. package/dist/hooks/useSlideUp.js.map +1 -1
  90. package/dist/hooks/useSmartMotion.d.ts +31 -0
  91. package/dist/hooks/useSmartMotion.d.ts.map +1 -0
  92. package/dist/hooks/useSmartMotion.js +257 -0
  93. package/dist/hooks/useSmartMotion.js.map +1 -0
  94. package/dist/hooks/useSpringMotion.d.ts +14 -24
  95. package/dist/hooks/useSpringMotion.d.ts.map +1 -1
  96. package/dist/hooks/useSpringMotion.js +109 -161
  97. package/dist/hooks/useSpringMotion.js.map +1 -1
  98. package/dist/hooks/useToggleMotion.d.ts +16 -0
  99. package/dist/hooks/useToggleMotion.d.ts.map +1 -0
  100. package/dist/hooks/useToggleMotion.js +53 -0
  101. package/dist/hooks/useToggleMotion.js.map +1 -0
  102. package/dist/hooks/useUnifiedMotion.d.ts +51 -0
  103. package/dist/hooks/useUnifiedMotion.d.ts.map +1 -0
  104. package/dist/hooks/useUnifiedMotion.js +106 -0
  105. package/dist/hooks/useUnifiedMotion.js.map +1 -0
  106. package/dist/index.d.ts +14 -10
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +37 -17
  109. package/dist/index.js.map +1 -1
  110. package/dist/managers/MotionStateManager.d.ts +63 -0
  111. package/dist/managers/MotionStateManager.d.ts.map +1 -0
  112. package/dist/managers/MotionStateManager.js +159 -0
  113. package/dist/managers/MotionStateManager.js.map +1 -0
  114. package/dist/presets/index.d.ts +16 -0
  115. package/dist/presets/index.d.ts.map +1 -0
  116. package/dist/presets/index.js +120 -0
  117. package/dist/presets/index.js.map +1 -0
  118. package/dist/types/common.d.ts +155 -0
  119. package/dist/types/common.d.ts.map +1 -0
  120. package/dist/types/common.js +5 -0
  121. package/dist/types/common.js.map +1 -0
  122. package/dist/types/index.d.ts +63 -95
  123. package/dist/types/index.d.ts.map +1 -1
  124. package/dist/types/index.js +2 -2
  125. package/dist/types/index.js.map +1 -1
  126. package/dist/utils/easing.d.ts +80 -26
  127. package/dist/utils/easing.d.ts.map +1 -1
  128. package/dist/utils/easing.js +202 -67
  129. package/dist/utils/easing.js.map +1 -1
  130. package/package.json +15 -15
@@ -0,0 +1,257 @@
1
+ import { useRef, useEffect, useState, useCallback, useMemo } from 'react';
2
+ /**
3
+ * 3단계 API: 개별 요소 모션
4
+ *
5
+ * 사용법:
6
+ * ```typescript
7
+ * const heroRef = useSmartMotion({ type: 'hero' })
8
+ * const titleRef = useSmartMotion({ type: 'title' })
9
+ * const buttonRef = useSmartMotion({ type: 'button' })
10
+ * ```
11
+ */
12
+ export function useSmartMotion(options = {}) {
13
+ const { type = 'text', entrance: customEntrance, hover: customHover, click: customClick, delay: customDelay, duration: customDuration, threshold = 0.1, autoLanguageSync = false } = options;
14
+ // 프리셋 설정
15
+ const getPresetConfig = useCallback(() => {
16
+ const presets = {
17
+ hero: { entrance: 'fadeIn', delay: 200, duration: 800, hover: false, click: false },
18
+ title: { entrance: 'slideUp', delay: 400, duration: 700, hover: false, click: false },
19
+ button: { entrance: 'scaleIn', delay: 600, duration: 300, hover: true, click: true },
20
+ card: { entrance: 'slideUp', delay: 800, duration: 500, hover: true, click: false },
21
+ text: { entrance: 'fadeIn', delay: 200, duration: 600, hover: false, click: false },
22
+ image: { entrance: 'scaleIn', delay: 400, duration: 600, hover: true, click: false }
23
+ };
24
+ return presets[type] || presets.text;
25
+ }, [type]);
26
+ const preset = getPresetConfig();
27
+ // 프리셋과 커스텀 옵션 병합
28
+ const entrance = customEntrance || preset.entrance;
29
+ const hover = customHover !== undefined ? customHover : preset.hover;
30
+ const click = customClick !== undefined ? customClick : preset.click;
31
+ const delay = customDelay !== undefined ? customDelay : preset.delay;
32
+ const duration = customDuration !== undefined ? customDuration : preset.duration;
33
+ const elementRef = useRef(null);
34
+ // 초기 모션 값 계산
35
+ const getInitialMotionValues = () => {
36
+ const initialState = {
37
+ isVisible: false,
38
+ isHovered: false,
39
+ isClicked: false,
40
+ opacity: 0,
41
+ translateY: 0,
42
+ translateX: 0,
43
+ scale: 1
44
+ };
45
+ // 초기 상태에 맞는 모션 값 설정
46
+ switch (entrance) {
47
+ case 'fadeIn':
48
+ initialState.opacity = 0;
49
+ break;
50
+ case 'slideUp':
51
+ initialState.opacity = 0;
52
+ initialState.translateY = 20;
53
+ break;
54
+ case 'slideLeft':
55
+ initialState.opacity = 0;
56
+ initialState.translateX = -20;
57
+ break;
58
+ case 'slideRight':
59
+ initialState.opacity = 0;
60
+ initialState.translateX = 20;
61
+ break;
62
+ case 'scaleIn':
63
+ initialState.opacity = 0;
64
+ initialState.scale = 0.8;
65
+ break;
66
+ case 'bounceIn':
67
+ initialState.opacity = 0;
68
+ initialState.scale = 0.5;
69
+ break;
70
+ }
71
+ return initialState;
72
+ };
73
+ const [state, setState] = useState(() => {
74
+ const initialValues = getInitialMotionValues();
75
+ // threshold가 0이면 즉시 visible로 설정
76
+ if (threshold === 0) {
77
+ initialValues.isVisible = true;
78
+ initialValues.opacity = 1;
79
+ initialValues.translateY = 0;
80
+ initialValues.translateX = 0;
81
+ initialValues.scale = 1;
82
+ }
83
+ return initialValues;
84
+ });
85
+ // 모션 값 계산
86
+ const calculateMotionValues = useCallback((currentState) => {
87
+ const { isVisible, isHovered, isClicked } = currentState;
88
+ let opacity = 0;
89
+ let translateY = 0;
90
+ let translateX = 0;
91
+ let scale = 1;
92
+ // 진입 모션
93
+ if (isVisible) {
94
+ opacity = 1;
95
+ switch (entrance) {
96
+ case 'fadeIn':
97
+ // 기본값 유지
98
+ break;
99
+ case 'slideUp':
100
+ translateY = 0;
101
+ break;
102
+ case 'slideLeft':
103
+ translateX = 0;
104
+ break;
105
+ case 'slideRight':
106
+ translateX = 0;
107
+ break;
108
+ case 'scaleIn':
109
+ scale = 1;
110
+ break;
111
+ case 'bounceIn':
112
+ scale = 1;
113
+ break;
114
+ }
115
+ }
116
+ else {
117
+ // 초기 상태
118
+ switch (entrance) {
119
+ case 'fadeIn':
120
+ opacity = 0;
121
+ break;
122
+ case 'slideUp':
123
+ opacity = 0;
124
+ translateY = 20;
125
+ break;
126
+ case 'slideLeft':
127
+ opacity = 0;
128
+ translateX = -20;
129
+ break;
130
+ case 'slideRight':
131
+ opacity = 0;
132
+ translateX = 20;
133
+ break;
134
+ case 'scaleIn':
135
+ opacity = 0;
136
+ scale = 0.8;
137
+ break;
138
+ case 'bounceIn':
139
+ opacity = 0;
140
+ scale = 0.5;
141
+ break;
142
+ }
143
+ }
144
+ // 호버 효과
145
+ if (hover && isHovered) {
146
+ scale *= 1.1; // 더 큰 확대
147
+ translateY -= 5; // 더 큰 이동
148
+ }
149
+ // 클릭 효과
150
+ if (click && isClicked) {
151
+ scale *= 0.85; // 더 큰 축소
152
+ translateY += 3; // 더 큰 이동
153
+ }
154
+ return { opacity, translateY, translateX, scale };
155
+ }, [entrance, hover, click]);
156
+ // Intersection Observer
157
+ useEffect(() => {
158
+ if (!elementRef.current)
159
+ return;
160
+ const observer = new IntersectionObserver((entries) => {
161
+ entries.forEach((entry) => {
162
+ if (entry.isIntersecting) {
163
+ setTimeout(() => {
164
+ setState(prev => ({ ...prev, isVisible: true }));
165
+ }, delay);
166
+ }
167
+ });
168
+ }, { threshold });
169
+ observer.observe(elementRef.current);
170
+ return () => {
171
+ observer.disconnect();
172
+ };
173
+ }, [delay, threshold]);
174
+ // 호버 이벤트
175
+ useEffect(() => {
176
+ if (!hover || !elementRef.current)
177
+ return;
178
+ const element = elementRef.current;
179
+ const handleMouseEnter = () => {
180
+ setState(prev => ({ ...prev, isHovered: true }));
181
+ };
182
+ const handleMouseLeave = () => {
183
+ setState(prev => ({ ...prev, isHovered: false }));
184
+ };
185
+ element.addEventListener('mouseenter', handleMouseEnter);
186
+ element.addEventListener('mouseleave', handleMouseLeave);
187
+ return () => {
188
+ element.removeEventListener('mouseenter', handleMouseEnter);
189
+ element.removeEventListener('mouseleave', handleMouseLeave);
190
+ };
191
+ }, [hover]);
192
+ // 클릭 이벤트
193
+ useEffect(() => {
194
+ if (!click || !elementRef.current)
195
+ return;
196
+ const element = elementRef.current;
197
+ const handleClick = () => {
198
+ setState(prev => ({ ...prev, isClicked: true }));
199
+ setTimeout(() => {
200
+ setState(prev => ({ ...prev, isClicked: false }));
201
+ }, 300); // 더 긴 지속시간
202
+ };
203
+ element.addEventListener('click', handleClick);
204
+ return () => {
205
+ element.removeEventListener('click', handleClick);
206
+ };
207
+ }, [click]);
208
+ // 상태 변경 시 모션 값 업데이트 (무한 루프 방지)
209
+ useEffect(() => {
210
+ setState(prev => {
211
+ const { opacity, translateY, translateX, scale } = calculateMotionValues(prev);
212
+ // 값이 실제로 변경되었을 때만 업데이트 (불필요한 리렌더링 방지)
213
+ if (prev.opacity === opacity &&
214
+ prev.translateY === translateY &&
215
+ prev.translateX === translateX &&
216
+ prev.scale === scale) {
217
+ return prev; // 변경 없으면 이전 상태 반환
218
+ }
219
+ return { ...prev, opacity, translateY, translateX, scale };
220
+ });
221
+ }, [state.isVisible, state.isHovered, state.isClicked, calculateMotionValues]);
222
+ // 언어 변경 감지 (간단한 버전)
223
+ useEffect(() => {
224
+ if (!autoLanguageSync)
225
+ return;
226
+ const handleLanguageChange = () => {
227
+ // 언어 변경 시 모션 재시작
228
+ setState(prev => ({ ...prev, isVisible: false }));
229
+ setTimeout(() => {
230
+ setState(prev => ({ ...prev, isVisible: true }));
231
+ }, 100);
232
+ };
233
+ // 간단한 언어 변경 감지 (실제로는 i18n 훅과 연동 필요)
234
+ window.addEventListener('storage', handleLanguageChange);
235
+ return () => {
236
+ window.removeEventListener('storage', handleLanguageChange);
237
+ };
238
+ }, [autoLanguageSync]);
239
+ // 스타일 메모이제이션으로 불필요한 리렌더링 방지
240
+ const motionStyle = useMemo(() => ({
241
+ opacity: state.opacity,
242
+ transform: `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`,
243
+ transition: `all ${duration}ms ease-out`,
244
+ // CSS transition과 충돌 방지
245
+ pointerEvents: 'auto',
246
+ // 강제로 스타일 적용
247
+ willChange: 'transform, opacity'
248
+ }), [state.opacity, state.translateX, state.translateY, state.scale, duration]);
249
+ return {
250
+ ref: elementRef,
251
+ style: motionStyle,
252
+ isVisible: state.isVisible,
253
+ isHovered: state.isHovered,
254
+ isClicked: state.isClicked
255
+ };
256
+ }
257
+ //# sourceMappingURL=useSmartMotion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSmartMotion.js","sourceRoot":"","sources":["../../src/hooks/useSmartMotion.ts"],"names":[],"mappings":"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"}
@@ -1,32 +1,22 @@
1
- import { BaseMotionOptions, BaseMotionReturn, MotionElement } from '../types';
2
- export interface SpringOptions extends BaseMotionOptions {
1
+ import { BaseMotionReturn, MotionElement } from '../types';
2
+ interface SpringConfig {
3
+ mass?: number;
3
4
  stiffness?: number;
4
5
  damping?: number;
5
- mass?: number;
6
6
  restDelta?: number;
7
7
  restSpeed?: number;
8
- initialScale?: number;
9
- initialOpacity?: number;
10
- initialRotate?: number;
11
- initialTranslateY?: number;
12
- initialTranslateX?: number;
13
- targetScale?: number;
14
- targetOpacity?: number;
15
- targetRotate?: number;
16
- targetTranslateY?: number;
17
- targetTranslateX?: number;
8
+ }
9
+ interface SpringMotionOptions {
10
+ from: number;
11
+ to: number;
12
+ config?: SpringConfig;
13
+ onComplete?: () => void;
14
+ enabled?: boolean;
18
15
  autoStart?: boolean;
19
16
  }
20
- export declare function useSpringMotion<T extends MotionElement = HTMLDivElement>(options?: SpringOptions): BaseMotionReturn<T> & {
21
- stiffness: number;
22
- damping: number;
23
- mass: number;
24
- velocity: {
25
- scale: number;
26
- opacity: number;
27
- rotate: number;
28
- translateY: number;
29
- translateX: number;
30
- };
17
+ export declare function useSpringMotion<T extends MotionElement = HTMLDivElement>(options: SpringMotionOptions): BaseMotionReturn<T> & {
18
+ value: number;
19
+ velocity: number;
31
20
  };
21
+ export {};
32
22
  //# sourceMappingURL=useSpringMotion.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSpringMotion.d.ts","sourceRoot":"","sources":["../../src/hooks/useSpringMotion.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE7E,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,aAAa,GAAG,cAAc,EACtE,OAAO,GAAE,aAAkB,GAC1B,gBAAgB,CAAC,CAAC,CAAC,GAAG;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;CACrG,CAiOA"}
1
+ {"version":3,"file":"useSpringMotion.d.ts","sourceRoot":"","sources":["../../src/hooks/useSpringMotion.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE1D,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,aAAa,GAAG,cAAc,EACtE,OAAO,EAAE,mBAAmB,GAC3B,gBAAgB,CAAC,CAAC,CAAC,GAAG;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAsKA"}
@@ -1,185 +1,133 @@
1
- import { useRef, useState, useEffect, useCallback } from 'react';
2
- export function useSpringMotion(options = {}) {
3
- const { duration = 1000, easing = 'ease-out', stiffness = 100, damping = 10, mass = 1, restDelta = 0.01, restSpeed = 0.01, initialScale = 0, initialOpacity = 0, initialRotate = 0, initialTranslateY = 0, initialTranslateX = 0, targetScale = 1, targetOpacity = 1, targetRotate = 0, targetTranslateY = 0, targetTranslateX = 0, autoStart = true, onComplete, onStart, onStop, onReset } = options;
1
+ import { useRef, useEffect, useState, useCallback, useMemo } from 'react';
2
+ export function useSpringMotion(options) {
3
+ const { from, to, config = {
4
+ mass: 1,
5
+ stiffness: 100,
6
+ damping: 10,
7
+ restDelta: 0.01,
8
+ restSpeed: 0.01
9
+ }, onComplete, enabled = true, autoStart = false } = options;
4
10
  const ref = useRef(null);
5
- const [isVisible, setIsVisible] = useState(false);
6
- const [isAnimating, setIsAnimating] = useState(false);
7
- const [progress, setProgress] = useState(0);
8
- // 현재 위치와 속도
9
- const [currentScale, setCurrentScale] = useState(initialScale);
10
- const [currentOpacity, setCurrentOpacity] = useState(initialOpacity);
11
- const [currentRotate, setCurrentRotate] = useState(initialRotate);
12
- const [currentTranslateY, setCurrentTranslateY] = useState(initialTranslateY);
13
- const [currentTranslateX, setCurrentTranslateX] = useState(initialTranslateX);
14
- // velocity를 useRef로 변경하여 의존성 배열 문제 해결
15
- const velocityRef = useRef({
16
- scale: 0,
17
- opacity: 0,
18
- rotate: 0,
19
- translateY: 0,
20
- translateX: 0
11
+ const [springState, setSpringState] = useState({
12
+ value: from,
13
+ velocity: 0,
14
+ isAnimating: false
21
15
  });
22
- // 스프링 애니메이션 루프
23
- const springAnimation = useCallback(() => {
24
- if (!isAnimating)
16
+ const [isVisible, setIsVisible] = useState(true);
17
+ const [progress, setProgress] = useState(0);
18
+ const motionRef = useRef(null);
19
+ const lastTimeRef = useRef(0);
20
+ // 스프링 물리 계산
21
+ const calculateSpring = useCallback((currentValue, currentVelocity, targetValue, deltaTime) => {
22
+ const { mass = 1, stiffness = 100, damping = 10 } = config;
23
+ // 스프링 힘 계산 (Hooke's Law)
24
+ const displacement = currentValue - targetValue;
25
+ const springForce = -stiffness * displacement;
26
+ // 댐핑 힘 계산
27
+ const dampingForce = -damping * currentVelocity;
28
+ // 총 힘
29
+ const totalForce = springForce + dampingForce;
30
+ // 가속도 (F = ma)
31
+ const acceleration = totalForce / mass;
32
+ // 새로운 속도
33
+ const newVelocity = currentVelocity + acceleration * deltaTime;
34
+ // 새로운 위치
35
+ const newValue = currentValue + newVelocity * deltaTime;
36
+ return { value: newValue, velocity: newVelocity };
37
+ }, [config]);
38
+ // 모션 루프
39
+ const animate = useCallback((currentTime) => {
40
+ if (!enabled || !springState.isAnimating)
25
41
  return;
26
- const animate = () => {
27
- if (!isAnimating)
28
- return;
29
- // 스프링 물리 계산
30
- const scaleForce = (targetScale - currentScale) * stiffness;
31
- const opacityForce = (targetOpacity - currentOpacity) * stiffness;
32
- const rotateForce = (targetRotate - currentRotate) * stiffness;
33
- const translateYForce = (targetTranslateY - currentTranslateY) * stiffness;
34
- const translateXForce = (targetTranslateX - currentTranslateX) * stiffness;
35
- // 가속도 계산 (F = ma)
36
- const scaleAcceleration = scaleForce / mass;
37
- const opacityAcceleration = opacityForce / mass;
38
- const rotateAcceleration = rotateForce / mass;
39
- const translateYAcceleration = translateYForce / mass;
40
- const translateXAcceleration = translateXForce / mass;
41
- // 속도 업데이트 (v = v0 + at)
42
- const newVelocity = {
43
- scale: velocityRef.current.scale + scaleAcceleration,
44
- opacity: velocityRef.current.opacity + opacityAcceleration,
45
- rotate: velocityRef.current.rotate + rotateAcceleration,
46
- translateY: velocityRef.current.translateY + translateYAcceleration,
47
- translateX: velocityRef.current.translateX + translateXAcceleration
48
- };
49
- // 감쇠 적용
50
- newVelocity.scale *= (1 - damping / 100);
51
- newVelocity.opacity *= (1 - damping / 100);
52
- newVelocity.rotate *= (1 - damping / 100);
53
- newVelocity.translateY *= (1 - damping / 100);
54
- newVelocity.translateX *= (1 - damping / 100);
55
- // velocityRef 업데이트
56
- velocityRef.current = newVelocity;
57
- // 위치 업데이트 (x = x0 + vt)
58
- const newScale = currentScale + newVelocity.scale;
59
- const newOpacity = currentOpacity + newVelocity.opacity;
60
- const newRotate = currentRotate + newVelocity.rotate;
61
- const newTranslateY = currentTranslateY + newVelocity.translateY;
62
- const newTranslateX = currentTranslateX + newVelocity.translateX;
63
- setCurrentScale(newScale);
64
- setCurrentOpacity(newOpacity);
65
- setCurrentRotate(newRotate);
66
- setCurrentTranslateY(newTranslateY);
67
- setCurrentTranslateX(newTranslateX);
68
- // 진행률 계산
69
- const totalDistance = Math.abs(targetScale - initialScale) +
70
- Math.abs(targetOpacity - initialOpacity) +
71
- Math.abs(targetRotate - initialRotate) +
72
- Math.abs(targetTranslateY - initialTranslateY) +
73
- Math.abs(targetTranslateX - initialTranslateX);
74
- const currentDistance = Math.abs(newScale - initialScale) +
75
- Math.abs(newOpacity - initialOpacity) +
76
- Math.abs(newRotate - initialRotate) +
77
- Math.abs(newTranslateY - initialTranslateY) +
78
- Math.abs(newTranslateX - initialTranslateX);
79
- const newProgress = totalDistance > 0 ? currentDistance / totalDistance : 0;
80
- setProgress(Math.min(newProgress, 1));
81
- // 정지 조건 확인
82
- const isAtRest = Math.abs(newVelocity.scale) < restSpeed &&
83
- Math.abs(newVelocity.opacity) < restSpeed &&
84
- Math.abs(newVelocity.rotate) < restSpeed &&
85
- Math.abs(newVelocity.translateY) < restSpeed &&
86
- Math.abs(newVelocity.translateX) < restSpeed &&
87
- Math.abs(targetScale - newScale) < restDelta &&
88
- Math.abs(targetOpacity - newOpacity) < restDelta &&
89
- Math.abs(targetRotate - newRotate) < restDelta &&
90
- Math.abs(targetTranslateY - newTranslateY) < restDelta &&
91
- Math.abs(targetTranslateX - newTranslateX) < restDelta;
92
- if (isAtRest) {
93
- // 목표값으로 정확히 설정
94
- setCurrentScale(targetScale);
95
- setCurrentOpacity(targetOpacity);
96
- setCurrentRotate(targetRotate);
97
- setCurrentTranslateY(targetTranslateY);
98
- setCurrentTranslateX(targetTranslateX);
99
- setProgress(1);
100
- setIsAnimating(false);
101
- onComplete?.();
102
- }
103
- else {
104
- requestAnimationFrame(animate);
105
- }
106
- };
107
- requestAnimationFrame(animate);
108
- }, [isAnimating, currentScale, currentOpacity, currentRotate, currentTranslateY, currentTranslateX,
109
- targetScale, targetOpacity, targetRotate, targetTranslateY, targetTranslateX,
110
- stiffness, damping, mass, restSpeed, restDelta, onComplete]);
111
- // 모션 시작 함수 - 조건 로직 개선
42
+ const deltaTime = Math.min(currentTime - lastTimeRef.current, 16) / 1000; // 60fps 제한
43
+ lastTimeRef.current = currentTime;
44
+ const { value, velocity } = calculateSpring(springState.value, springState.velocity, to, deltaTime);
45
+ // 진행률 계산
46
+ const range = Math.abs(to - from);
47
+ const currentProgress = range > 0 ? Math.min(Math.abs(value - from) / range, 1) : 1;
48
+ setProgress(currentProgress);
49
+ // 정지 조건 확인
50
+ const isAtRest = Math.abs(value - to) < (config.restDelta || 0.01) && Math.abs(velocity) < (config.restSpeed || 0.01);
51
+ if (isAtRest) {
52
+ setSpringState({
53
+ value: to,
54
+ velocity: 0,
55
+ isAnimating: false
56
+ });
57
+ setProgress(1);
58
+ onComplete?.();
59
+ return;
60
+ }
61
+ setSpringState({
62
+ value,
63
+ velocity,
64
+ isAnimating: true
65
+ });
66
+ motionRef.current = requestAnimationFrame(animate);
67
+ }, [enabled, springState.isAnimating, to, config, onComplete, calculateSpring]);
68
+ // 모션 시작
112
69
  const start = useCallback(() => {
113
- // 이미 애니메이션 중이거나 보이는 상태라면 무시
114
- if (isAnimating)
70
+ if (springState.isAnimating)
115
71
  return;
116
- setIsVisible(true);
117
- setIsAnimating(true);
118
- setProgress(0);
119
- onStart?.();
120
- springAnimation();
121
- }, [isAnimating, onStart, springAnimation]);
122
- // 모션 중단 함수
72
+ setSpringState(prev => ({
73
+ ...prev,
74
+ isAnimating: true
75
+ }));
76
+ lastTimeRef.current = performance.now();
77
+ motionRef.current = requestAnimationFrame(animate);
78
+ }, [springState.isAnimating, animate]);
79
+ // 모션 정지
123
80
  const stop = useCallback(() => {
124
- setIsAnimating(false);
125
- onStop?.();
126
- }, [onStop]);
127
- // 모션 리셋 함수
81
+ if (motionRef.current) {
82
+ cancelAnimationFrame(motionRef.current);
83
+ motionRef.current = null;
84
+ }
85
+ setSpringState(prev => ({
86
+ ...prev,
87
+ isAnimating: false
88
+ }));
89
+ }, []);
90
+ // 모션 리셋
128
91
  const reset = useCallback(() => {
129
- setIsVisible(false);
130
- setIsAnimating(false);
92
+ stop();
93
+ setSpringState({
94
+ value: from,
95
+ velocity: 0,
96
+ isAnimating: false
97
+ });
131
98
  setProgress(0);
132
- setCurrentScale(initialScale);
133
- setCurrentOpacity(initialOpacity);
134
- setCurrentRotate(initialRotate);
135
- setCurrentTranslateY(initialTranslateY);
136
- setCurrentTranslateX(initialTranslateX);
137
- // velocityRef 초기화
138
- velocityRef.current = { scale: 0, opacity: 0, rotate: 0, translateY: 0, translateX: 0 };
139
- onReset?.();
140
- }, [initialScale, initialOpacity, initialRotate, initialTranslateY, initialTranslateX, onReset]);
141
- // 모션 일시정지 함수
142
- const pause = useCallback(() => {
143
- setIsAnimating(false);
144
- }, []);
145
- // 모션 재개 함수
146
- const resume = useCallback(() => {
147
- if (isVisible && !isAnimating) {
148
- setIsAnimating(true);
149
- springAnimation();
150
- }
151
- }, [isVisible, isAnimating, springAnimation]);
99
+ motionRef.current = null;
100
+ }, [from, stop]);
152
101
  // 자동 시작
153
102
  useEffect(() => {
154
103
  if (autoStart) {
155
104
  start();
156
105
  }
157
106
  }, [autoStart, start]);
158
- // 스프링 스타일 계산
159
- const style = {
160
- transform: `
161
- scale(${currentScale})
162
- rotate(${currentRotate}deg)
163
- translate(${currentTranslateX}px, ${currentTranslateY}px)
164
- `,
165
- opacity: currentOpacity,
166
- willChange: 'transform, opacity'
167
- };
107
+ // 컴포넌트 언마운트 시 정리
108
+ useEffect(() => {
109
+ return () => {
110
+ if (motionRef.current) {
111
+ cancelAnimationFrame(motionRef.current);
112
+ }
113
+ };
114
+ }, []);
115
+ // 스타일 계산
116
+ const style = useMemo(() => ({
117
+ '--motion-progress': `${progress}`,
118
+ '--motion-value': `${springState.value}`
119
+ }), [progress, springState.value]);
168
120
  return {
169
121
  ref,
170
122
  isVisible,
171
- isAnimating,
123
+ isAnimating: springState.isAnimating,
172
124
  style,
173
125
  progress,
126
+ value: springState.value,
127
+ velocity: springState.velocity,
174
128
  start,
175
129
  stop,
176
- reset,
177
- pause,
178
- resume,
179
- stiffness,
180
- damping,
181
- mass,
182
- velocity: velocityRef.current // 현재 velocity 값 반환
130
+ reset
183
131
  };
184
132
  }
185
133
  //# sourceMappingURL=useSpringMotion.js.map