@hua-labs/motion-core 2.3.0 → 2.4.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.
- package/LICENSE +21 -0
- package/README.md +128 -39
- package/dist/chunk-47QAGQLN.mjs +1057 -0
- package/dist/chunk-47QAGQLN.mjs.map +1 -0
- package/dist/chunk-LSIP7MB5.cjs +1090 -0
- package/dist/chunk-LSIP7MB5.cjs.map +1 -0
- package/dist/index.cjs +7591 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +637 -685
- package/dist/index.d.ts +1579 -0
- package/dist/index.mjs +2788 -1175
- package/dist/index.mjs.map +1 -1
- package/dist/native.cjs +692 -0
- package/dist/native.cjs.map +1 -0
- package/dist/native.d.mts +177 -0
- package/dist/native.d.ts +177 -0
- package/dist/native.mjs +555 -0
- package/dist/native.mjs.map +1 -0
- package/dist/springPhysics-BZVRi9PQ.d.mts +740 -0
- package/dist/springPhysics-BZVRi9PQ.d.ts +740 -0
- package/package.json +29 -9
package/dist/index.mjs
CHANGED
|
@@ -1,559 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { getPagePreset, getMotionPreset, mergeWithPreset, motionStateManager, MOTION_PRESETS, useMotionProfile, getEasing, calculateSpring } from './chunk-47QAGQLN.mjs';
|
|
3
|
+
export { MOTION_PRESETS, MotionEngine, MotionProfileProvider, PAGE_MOTIONS, TransitionEffects, applyEasing, calculateSpring, easeIn, easeInOut, easeInOutQuad, easeInQuad, easeOut, easeOutQuad, easingPresets, getAvailableEasings, getEasing, getMotionPreset, getPagePreset, getPresetEasing, hua, isEasingFunction, isValidEasing, linear, mergeProfileOverrides, mergeWithPreset, motionEngine, neutral, resolveProfile, safeApplyEasing, transitionEffects, useMotionProfile } from './chunk-47QAGQLN.mjs';
|
|
4
|
+
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
3
5
|
import { jsx } from 'react/jsx-runtime';
|
|
4
6
|
|
|
5
|
-
// src/core/MotionEngine.ts
|
|
6
|
-
var MotionEngine = class {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.motions = /* @__PURE__ */ new Map();
|
|
9
|
-
this.isRunning = false;
|
|
10
|
-
this.animationFrameId = null;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* 모션 시작
|
|
14
|
-
*/
|
|
15
|
-
motion(element, motionFrames, options) {
|
|
16
|
-
return new Promise((resolve) => {
|
|
17
|
-
const motionId = this.generateMotionId();
|
|
18
|
-
this.enableGPUAcceleration(element);
|
|
19
|
-
this.createLayer(element);
|
|
20
|
-
const motion = {
|
|
21
|
-
id: motionId,
|
|
22
|
-
element,
|
|
23
|
-
isRunning: true,
|
|
24
|
-
isPaused: false,
|
|
25
|
-
currentProgress: 0,
|
|
26
|
-
startTime: Date.now() + (options.delay || 0),
|
|
27
|
-
pauseTime: 0,
|
|
28
|
-
options: {
|
|
29
|
-
...options,
|
|
30
|
-
onComplete: () => {
|
|
31
|
-
options.onComplete?.();
|
|
32
|
-
this.motions.delete(motionId);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
this.motions.set(motionId, motion);
|
|
37
|
-
if (!this.isRunning) {
|
|
38
|
-
this.startAnimationLoop();
|
|
39
|
-
}
|
|
40
|
-
options.onStart?.();
|
|
41
|
-
resolve(motionId);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* 모션 중지
|
|
46
|
-
*/
|
|
47
|
-
stop(motionId) {
|
|
48
|
-
const motion = this.motions.get(motionId);
|
|
49
|
-
if (motion) {
|
|
50
|
-
motion.isRunning = false;
|
|
51
|
-
motion.options.onCancel?.();
|
|
52
|
-
this.motions.delete(motionId);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* 모션 일시정지
|
|
57
|
-
*/
|
|
58
|
-
pause(motionId) {
|
|
59
|
-
const motion = this.motions.get(motionId);
|
|
60
|
-
if (motion && motion.isRunning && !motion.isPaused) {
|
|
61
|
-
motion.isPaused = true;
|
|
62
|
-
motion.pauseTime = Date.now();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* 모션 재개
|
|
67
|
-
*/
|
|
68
|
-
resume(motionId) {
|
|
69
|
-
const motion = this.motions.get(motionId);
|
|
70
|
-
if (motion && motion.isPaused) {
|
|
71
|
-
motion.isPaused = false;
|
|
72
|
-
if (motion.pauseTime > 0) {
|
|
73
|
-
motion.startTime += Date.now() - motion.pauseTime;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* 모든 모션 중지
|
|
79
|
-
*/
|
|
80
|
-
stopAll() {
|
|
81
|
-
this.motions.forEach((motion) => {
|
|
82
|
-
motion.isRunning = false;
|
|
83
|
-
motion.options.onCancel?.();
|
|
84
|
-
});
|
|
85
|
-
this.motions.clear();
|
|
86
|
-
this.stopAnimationLoop();
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* 모션 상태 확인
|
|
90
|
-
*/
|
|
91
|
-
getMotion(motionId) {
|
|
92
|
-
return this.motions.get(motionId);
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* 실행 중인 모션 수
|
|
96
|
-
*/
|
|
97
|
-
getActiveMotionCount() {
|
|
98
|
-
return this.motions.size;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* 애니메이션 루프 시작
|
|
102
|
-
*/
|
|
103
|
-
startAnimationLoop() {
|
|
104
|
-
if (this.isRunning) return;
|
|
105
|
-
this.isRunning = true;
|
|
106
|
-
this.animate();
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* 애니메이션 루프 중지
|
|
110
|
-
*/
|
|
111
|
-
stopAnimationLoop() {
|
|
112
|
-
if (this.animationFrameId) {
|
|
113
|
-
cancelAnimationFrame(this.animationFrameId);
|
|
114
|
-
this.animationFrameId = null;
|
|
115
|
-
}
|
|
116
|
-
this.isRunning = false;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* 메인 애니메이션 루프
|
|
120
|
-
*/
|
|
121
|
-
animate() {
|
|
122
|
-
if (!this.isRunning || this.motions.size === 0) {
|
|
123
|
-
this.stopAnimationLoop();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const currentTime = Date.now();
|
|
127
|
-
const completedMotions = [];
|
|
128
|
-
this.motions.forEach((motion) => {
|
|
129
|
-
if (!motion.isRunning || motion.isPaused) return;
|
|
130
|
-
const elapsed = currentTime - motion.startTime;
|
|
131
|
-
if (elapsed < 0) return;
|
|
132
|
-
const progress = Math.min(elapsed / motion.options.duration, 1);
|
|
133
|
-
const easedProgress = motion.options.easing(progress);
|
|
134
|
-
motion.currentProgress = easedProgress;
|
|
135
|
-
this.applyMotionFrame(motion.element, easedProgress);
|
|
136
|
-
motion.options.onUpdate?.(easedProgress);
|
|
137
|
-
if (progress >= 1) {
|
|
138
|
-
completedMotions.push(motion.id);
|
|
139
|
-
motion.isRunning = false;
|
|
140
|
-
motion.options.onComplete?.();
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
completedMotions.forEach((id) => this.motions.delete(id));
|
|
144
|
-
this.animationFrameId = requestAnimationFrame(() => this.animate());
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* 모션 프레임 적용
|
|
148
|
-
*/
|
|
149
|
-
applyMotionFrame(element, progress) {
|
|
150
|
-
const transforms = [];
|
|
151
|
-
if (element.style.transform) {
|
|
152
|
-
transforms.push(element.style.transform);
|
|
153
|
-
}
|
|
154
|
-
element.style.transform = transforms.join(" ");
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* GPU 가속 활성화
|
|
158
|
-
*/
|
|
159
|
-
enableGPUAcceleration(element) {
|
|
160
|
-
element.style.willChange = "transform, opacity";
|
|
161
|
-
element.style.transform = "translateZ(0)";
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* 레이어 분리
|
|
165
|
-
*/
|
|
166
|
-
createLayer(element) {
|
|
167
|
-
element.style.transform = "translateZ(0)";
|
|
168
|
-
element.style.backfaceVisibility = "hidden";
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* 고유 모션 ID 생성
|
|
172
|
-
*/
|
|
173
|
-
generateMotionId() {
|
|
174
|
-
return `motion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* 정리
|
|
178
|
-
*/
|
|
179
|
-
destroy() {
|
|
180
|
-
this.stopAll();
|
|
181
|
-
this.stopAnimationLoop();
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
var motionEngine = new MotionEngine();
|
|
185
|
-
|
|
186
|
-
// src/core/TransitionEffects.ts
|
|
187
|
-
var TransitionEffects = class _TransitionEffects {
|
|
188
|
-
constructor() {
|
|
189
|
-
this.activeTransitions = /* @__PURE__ */ new Map();
|
|
190
|
-
}
|
|
191
|
-
static getInstance() {
|
|
192
|
-
if (!_TransitionEffects.instance) {
|
|
193
|
-
_TransitionEffects.instance = new _TransitionEffects();
|
|
194
|
-
}
|
|
195
|
-
return _TransitionEffects.instance;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* 공통 전환 실행 헬퍼
|
|
199
|
-
*/
|
|
200
|
-
async executeTransition(element, options, config) {
|
|
201
|
-
const transitionId = this.generateTransitionId();
|
|
202
|
-
config.setup();
|
|
203
|
-
this.enableGPUAcceleration(element);
|
|
204
|
-
let resolveTransition;
|
|
205
|
-
const completed = new Promise((resolve) => {
|
|
206
|
-
resolveTransition = resolve;
|
|
207
|
-
});
|
|
208
|
-
const motionId = await motionEngine.motion(
|
|
209
|
-
element,
|
|
210
|
-
[
|
|
211
|
-
{ progress: 0, properties: config.keyframes[0] },
|
|
212
|
-
{ progress: 1, properties: config.keyframes[1] }
|
|
213
|
-
],
|
|
214
|
-
{
|
|
215
|
-
duration: options.duration,
|
|
216
|
-
easing: options.easing || this.getDefaultEasing(),
|
|
217
|
-
delay: options.delay,
|
|
218
|
-
onStart: options.onTransitionStart,
|
|
219
|
-
onUpdate: config.onUpdate,
|
|
220
|
-
onComplete: () => {
|
|
221
|
-
config.onCleanup();
|
|
222
|
-
options.onTransitionComplete?.();
|
|
223
|
-
this.activeTransitions.delete(transitionId);
|
|
224
|
-
resolveTransition();
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
);
|
|
228
|
-
this.activeTransitions.set(transitionId, motionId);
|
|
229
|
-
return completed;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* 페이드 인/아웃 전환
|
|
233
|
-
*/
|
|
234
|
-
async fade(element, options) {
|
|
235
|
-
const initialOpacity = parseFloat(getComputedStyle(element).opacity) || 1;
|
|
236
|
-
const targetOpacity = options.direction === "reverse" ? 0 : 1;
|
|
237
|
-
return this.executeTransition(element, options, {
|
|
238
|
-
setup: () => {
|
|
239
|
-
if (options.direction === "reverse") {
|
|
240
|
-
element.style.opacity = initialOpacity.toString();
|
|
241
|
-
} else {
|
|
242
|
-
element.style.opacity = "0";
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
keyframes: [
|
|
246
|
-
{ opacity: options.direction === "reverse" ? initialOpacity : 0 },
|
|
247
|
-
{ opacity: targetOpacity }
|
|
248
|
-
],
|
|
249
|
-
onUpdate: (progress) => {
|
|
250
|
-
const currentOpacity = options.direction === "reverse" ? initialOpacity * (1 - progress) : targetOpacity * progress;
|
|
251
|
-
element.style.opacity = currentOpacity.toString();
|
|
252
|
-
},
|
|
253
|
-
onCleanup: () => {
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* 슬라이드 전환
|
|
259
|
-
*/
|
|
260
|
-
async slide(element, options) {
|
|
261
|
-
const distance = options.distance || 100;
|
|
262
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
263
|
-
const isReverse = options.direction === "reverse";
|
|
264
|
-
return this.executeTransition(element, options, {
|
|
265
|
-
setup: () => {
|
|
266
|
-
if (!isReverse) {
|
|
267
|
-
element.style.transform = `translateX(${distance}px)`;
|
|
268
|
-
}
|
|
269
|
-
},
|
|
270
|
-
keyframes: [
|
|
271
|
-
{ translateX: isReverse ? 0 : distance },
|
|
272
|
-
{ translateX: isReverse ? distance : 0 }
|
|
273
|
-
],
|
|
274
|
-
onUpdate: (progress) => {
|
|
275
|
-
const currentTranslateX = isReverse ? distance * progress : distance * (1 - progress);
|
|
276
|
-
element.style.transform = `translateX(${currentTranslateX}px)`;
|
|
277
|
-
},
|
|
278
|
-
onCleanup: () => {
|
|
279
|
-
element.style.transform = initialTransform;
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* 스케일 전환
|
|
285
|
-
*/
|
|
286
|
-
async scale(element, options) {
|
|
287
|
-
const scaleValue = options.scale || 0.8;
|
|
288
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
289
|
-
const isReverse = options.direction === "reverse";
|
|
290
|
-
return this.executeTransition(element, options, {
|
|
291
|
-
setup: () => {
|
|
292
|
-
if (!isReverse) {
|
|
293
|
-
element.style.transform = `scale(${scaleValue})`;
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
keyframes: [
|
|
297
|
-
{ scale: isReverse ? 1 : scaleValue },
|
|
298
|
-
{ scale: isReverse ? scaleValue : 1 }
|
|
299
|
-
],
|
|
300
|
-
onUpdate: (progress) => {
|
|
301
|
-
const currentScale = isReverse ? 1 - (1 - scaleValue) * progress : scaleValue + (1 - scaleValue) * progress;
|
|
302
|
-
element.style.transform = `scale(${currentScale})`;
|
|
303
|
-
},
|
|
304
|
-
onCleanup: () => {
|
|
305
|
-
element.style.transform = initialTransform;
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* 플립 전환 (3D 회전)
|
|
311
|
-
*/
|
|
312
|
-
async flip(element, options) {
|
|
313
|
-
const perspective = options.perspective || 1e3;
|
|
314
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
315
|
-
const isReverse = options.direction === "reverse";
|
|
316
|
-
return this.executeTransition(element, options, {
|
|
317
|
-
setup: () => {
|
|
318
|
-
element.style.perspective = `${perspective}px`;
|
|
319
|
-
element.style.transformStyle = "preserve-3d";
|
|
320
|
-
if (!isReverse) {
|
|
321
|
-
element.style.transform = `rotateY(90deg)`;
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
keyframes: [
|
|
325
|
-
{ rotateY: isReverse ? 0 : 90 },
|
|
326
|
-
{ rotateY: isReverse ? 90 : 0 }
|
|
327
|
-
],
|
|
328
|
-
onUpdate: (progress) => {
|
|
329
|
-
const currentRotateY = isReverse ? 90 * progress : 90 * (1 - progress);
|
|
330
|
-
element.style.transform = `rotateY(${currentRotateY}deg)`;
|
|
331
|
-
},
|
|
332
|
-
onCleanup: () => {
|
|
333
|
-
element.style.transform = initialTransform;
|
|
334
|
-
element.style.perspective = "";
|
|
335
|
-
element.style.transformStyle = "";
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* 큐브 전환 (3D 큐브 회전)
|
|
341
|
-
*/
|
|
342
|
-
async cube(element, options) {
|
|
343
|
-
const perspective = options.perspective || 1200;
|
|
344
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
345
|
-
const isReverse = options.direction === "reverse";
|
|
346
|
-
return this.executeTransition(element, options, {
|
|
347
|
-
setup: () => {
|
|
348
|
-
element.style.perspective = `${perspective}px`;
|
|
349
|
-
element.style.transformStyle = "preserve-3d";
|
|
350
|
-
if (!isReverse) {
|
|
351
|
-
element.style.transform = `rotateX(90deg) rotateY(45deg)`;
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
keyframes: [
|
|
355
|
-
{ rotateX: isReverse ? 0 : 90, rotateY: isReverse ? 0 : 45 },
|
|
356
|
-
{ rotateX: isReverse ? 90 : 0, rotateY: isReverse ? 45 : 0 }
|
|
357
|
-
],
|
|
358
|
-
onUpdate: (progress) => {
|
|
359
|
-
const currentRotateX = isReverse ? 90 * progress : 90 * (1 - progress);
|
|
360
|
-
const currentRotateY = isReverse ? 45 * progress : 45 * (1 - progress);
|
|
361
|
-
element.style.transform = `rotateX(${currentRotateX}deg) rotateY(${currentRotateY}deg)`;
|
|
362
|
-
},
|
|
363
|
-
onCleanup: () => {
|
|
364
|
-
element.style.transform = initialTransform;
|
|
365
|
-
element.style.perspective = "";
|
|
366
|
-
element.style.transformStyle = "";
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* 모프 전환 (복합 변형)
|
|
372
|
-
*/
|
|
373
|
-
async morph(element, options) {
|
|
374
|
-
const initialTransform = getComputedStyle(element).transform;
|
|
375
|
-
const isReverse = options.direction === "reverse";
|
|
376
|
-
return this.executeTransition(element, options, {
|
|
377
|
-
setup: () => {
|
|
378
|
-
if (!isReverse) {
|
|
379
|
-
element.style.transform = `scale(0.9) rotate(5deg)`;
|
|
380
|
-
}
|
|
381
|
-
},
|
|
382
|
-
keyframes: [
|
|
383
|
-
{ scale: isReverse ? 1 : 0.9, rotate: isReverse ? 0 : 5 },
|
|
384
|
-
{ scale: isReverse ? 0.9 : 1, rotate: isReverse ? 5 : 0 }
|
|
385
|
-
],
|
|
386
|
-
onUpdate: (progress) => {
|
|
387
|
-
const currentScale = isReverse ? 1 - 0.1 * progress : 0.9 + 0.1 * progress;
|
|
388
|
-
const currentRotate = isReverse ? 5 * progress : 5 * (1 - progress);
|
|
389
|
-
element.style.transform = `scale(${currentScale}) rotate(${currentRotate}deg)`;
|
|
390
|
-
},
|
|
391
|
-
onCleanup: () => {
|
|
392
|
-
element.style.transform = initialTransform;
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* 전환 중지
|
|
398
|
-
*/
|
|
399
|
-
stopTransition(transitionId) {
|
|
400
|
-
const motionId = this.activeTransitions.get(transitionId);
|
|
401
|
-
if (motionId) {
|
|
402
|
-
motionEngine.stop(motionId);
|
|
403
|
-
this.activeTransitions.delete(transitionId);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* 모든 전환 중지
|
|
408
|
-
*/
|
|
409
|
-
stopAllTransitions() {
|
|
410
|
-
this.activeTransitions.forEach((motionId) => {
|
|
411
|
-
motionEngine.stop(motionId);
|
|
412
|
-
});
|
|
413
|
-
this.activeTransitions.clear();
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* 활성 전환 수 확인
|
|
417
|
-
*/
|
|
418
|
-
getActiveTransitionCount() {
|
|
419
|
-
return this.activeTransitions.size;
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* GPU 가속 활성화
|
|
423
|
-
*/
|
|
424
|
-
enableGPUAcceleration(element) {
|
|
425
|
-
element.style.willChange = "transform, opacity";
|
|
426
|
-
const currentTransform = element.style.transform;
|
|
427
|
-
if (currentTransform && currentTransform !== "none" && currentTransform !== "") {
|
|
428
|
-
if (!currentTransform.includes("translateZ")) {
|
|
429
|
-
element.style.transform = `${currentTransform} translateZ(0)`;
|
|
430
|
-
}
|
|
431
|
-
} else {
|
|
432
|
-
element.style.transform = "translateZ(0)";
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* 기본 이징 함수
|
|
437
|
-
*/
|
|
438
|
-
getDefaultEasing() {
|
|
439
|
-
return (t) => t * t * (3 - 2 * t);
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* 고유 전환 ID 생성
|
|
443
|
-
*/
|
|
444
|
-
generateTransitionId() {
|
|
445
|
-
return `transition_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* 정리
|
|
449
|
-
*/
|
|
450
|
-
destroy() {
|
|
451
|
-
this.stopAllTransitions();
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
var transitionEffects = TransitionEffects.getInstance();
|
|
455
|
-
|
|
456
|
-
// src/presets/index.ts
|
|
457
|
-
var MOTION_PRESETS = {
|
|
458
|
-
hero: {
|
|
459
|
-
entrance: "fadeIn",
|
|
460
|
-
delay: 200,
|
|
461
|
-
duration: 800,
|
|
462
|
-
hover: false,
|
|
463
|
-
click: false
|
|
464
|
-
},
|
|
465
|
-
title: {
|
|
466
|
-
entrance: "slideUp",
|
|
467
|
-
delay: 400,
|
|
468
|
-
duration: 700,
|
|
469
|
-
hover: false,
|
|
470
|
-
click: false
|
|
471
|
-
},
|
|
472
|
-
button: {
|
|
473
|
-
entrance: "scaleIn",
|
|
474
|
-
delay: 600,
|
|
475
|
-
duration: 300,
|
|
476
|
-
hover: true,
|
|
477
|
-
click: true
|
|
478
|
-
},
|
|
479
|
-
card: {
|
|
480
|
-
entrance: "slideUp",
|
|
481
|
-
delay: 800,
|
|
482
|
-
duration: 500,
|
|
483
|
-
hover: true,
|
|
484
|
-
click: false
|
|
485
|
-
},
|
|
486
|
-
text: {
|
|
487
|
-
entrance: "fadeIn",
|
|
488
|
-
delay: 200,
|
|
489
|
-
duration: 600,
|
|
490
|
-
hover: false,
|
|
491
|
-
click: false
|
|
492
|
-
},
|
|
493
|
-
image: {
|
|
494
|
-
entrance: "scaleIn",
|
|
495
|
-
delay: 400,
|
|
496
|
-
duration: 600,
|
|
497
|
-
hover: true,
|
|
498
|
-
click: false
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
var PAGE_MOTIONS = {
|
|
502
|
-
// 홈페이지
|
|
503
|
-
home: {
|
|
504
|
-
hero: { type: "hero" },
|
|
505
|
-
title: { type: "title" },
|
|
506
|
-
description: { type: "text" },
|
|
507
|
-
cta: { type: "button" },
|
|
508
|
-
feature1: { type: "card" },
|
|
509
|
-
feature2: { type: "card" },
|
|
510
|
-
feature3: { type: "card" }
|
|
511
|
-
},
|
|
512
|
-
// 대시보드
|
|
513
|
-
dashboard: {
|
|
514
|
-
header: { type: "hero" },
|
|
515
|
-
sidebar: { type: "card", entrance: "slideLeft" },
|
|
516
|
-
main: { type: "text", entrance: "fadeIn" },
|
|
517
|
-
card1: { type: "card" },
|
|
518
|
-
card2: { type: "card" },
|
|
519
|
-
card3: { type: "card" },
|
|
520
|
-
chart: { type: "image" }
|
|
521
|
-
},
|
|
522
|
-
// 제품 페이지
|
|
523
|
-
product: {
|
|
524
|
-
hero: { type: "hero" },
|
|
525
|
-
title: { type: "title" },
|
|
526
|
-
image: { type: "image" },
|
|
527
|
-
description: { type: "text" },
|
|
528
|
-
price: { type: "text" },
|
|
529
|
-
buyButton: { type: "button" },
|
|
530
|
-
features: { type: "card" }
|
|
531
|
-
},
|
|
532
|
-
// 블로그
|
|
533
|
-
blog: {
|
|
534
|
-
header: { type: "hero" },
|
|
535
|
-
title: { type: "title" },
|
|
536
|
-
content: { type: "text" },
|
|
537
|
-
sidebar: { type: "card", entrance: "slideRight" },
|
|
538
|
-
related1: { type: "card" },
|
|
539
|
-
related2: { type: "card" },
|
|
540
|
-
related3: { type: "card" }
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
function mergeWithPreset(preset, custom = {}) {
|
|
544
|
-
return {
|
|
545
|
-
...preset,
|
|
546
|
-
...custom
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
function getPagePreset(pageType) {
|
|
550
|
-
return PAGE_MOTIONS[pageType];
|
|
551
|
-
}
|
|
552
|
-
function getMotionPreset(type) {
|
|
553
|
-
return MOTION_PRESETS[type] || MOTION_PRESETS.text;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// src/hooks/useSimplePageMotion.ts
|
|
557
7
|
function useSimplePageMotion(pageType) {
|
|
558
8
|
const config = getPagePreset(pageType);
|
|
559
9
|
return useSimplePageMotions(config);
|
|
@@ -658,156 +108,6 @@ function useSimplePageMotions(config) {
|
|
|
658
108
|
}, [motions]);
|
|
659
109
|
return getPageMotionRefs();
|
|
660
110
|
}
|
|
661
|
-
|
|
662
|
-
// src/managers/MotionStateManager.ts
|
|
663
|
-
var MotionStateManager = class {
|
|
664
|
-
constructor() {
|
|
665
|
-
this.states = /* @__PURE__ */ new Map();
|
|
666
|
-
this.listeners = /* @__PURE__ */ new Map();
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* 요소의 상태를 초기화
|
|
670
|
-
*/
|
|
671
|
-
initializeElement(elementId, config) {
|
|
672
|
-
const initialState = {
|
|
673
|
-
internalVisibility: false,
|
|
674
|
-
// 초기에 숨김 상태로 시작 (스크롤 리빌용)
|
|
675
|
-
triggeredVisibility: false,
|
|
676
|
-
// Intersection Observer가 아직 트리거되지 않음
|
|
677
|
-
finalVisibility: false,
|
|
678
|
-
// 초기에 숨김 상태로 시작
|
|
679
|
-
opacity: 0,
|
|
680
|
-
// 초기에 투명 상태로 시작
|
|
681
|
-
translateY: 20,
|
|
682
|
-
// 초기에 아래로 이동된 상태로 시작
|
|
683
|
-
translateX: 0,
|
|
684
|
-
scale: 0.95,
|
|
685
|
-
// 초기에 약간 축소된 상태로 시작
|
|
686
|
-
rotation: 0,
|
|
687
|
-
isHovered: false,
|
|
688
|
-
isClicked: false,
|
|
689
|
-
isAnimating: false
|
|
690
|
-
};
|
|
691
|
-
this.states.set(elementId, initialState);
|
|
692
|
-
this.computeFinalState(elementId);
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* 내부 가시성 상태 업데이트 (초기화, 리셋 등)
|
|
696
|
-
*/
|
|
697
|
-
setInternalVisibility(elementId, visible) {
|
|
698
|
-
const state = this.states.get(elementId);
|
|
699
|
-
if (!state) return;
|
|
700
|
-
state.internalVisibility = visible;
|
|
701
|
-
this.computeFinalState(elementId);
|
|
702
|
-
this.notifyListeners(elementId, state);
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* 외부 트리거 가시성 상태 업데이트 (Intersection Observer)
|
|
706
|
-
*/
|
|
707
|
-
setTriggeredVisibility(elementId, visible) {
|
|
708
|
-
const state = this.states.get(elementId);
|
|
709
|
-
if (!state) return;
|
|
710
|
-
state.triggeredVisibility = visible;
|
|
711
|
-
this.computeFinalState(elementId);
|
|
712
|
-
this.notifyListeners(elementId, state);
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* 모션 값 업데이트
|
|
716
|
-
*/
|
|
717
|
-
updateMotionValues(elementId, values) {
|
|
718
|
-
const state = this.states.get(elementId);
|
|
719
|
-
if (!state) return;
|
|
720
|
-
Object.assign(state, values);
|
|
721
|
-
this.notifyListeners(elementId, state);
|
|
722
|
-
}
|
|
723
|
-
/**
|
|
724
|
-
* 최종 상태 계산
|
|
725
|
-
*/
|
|
726
|
-
computeFinalState(elementId) {
|
|
727
|
-
const state = this.states.get(elementId);
|
|
728
|
-
if (!state) return;
|
|
729
|
-
state.finalVisibility = state.internalVisibility || state.triggeredVisibility;
|
|
730
|
-
state.isAnimating = state.finalVisibility && (state.opacity < 1 || state.translateY > 0);
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* 현재 상태 조회
|
|
734
|
-
*/
|
|
735
|
-
getState(elementId) {
|
|
736
|
-
return this.states.get(elementId);
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* 모든 상태 조회
|
|
740
|
-
*/
|
|
741
|
-
getAllStates() {
|
|
742
|
-
return new Map(this.states);
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* 상태 변경 리스너 등록
|
|
746
|
-
*/
|
|
747
|
-
subscribe(elementId, listener) {
|
|
748
|
-
if (!this.listeners.has(elementId)) {
|
|
749
|
-
this.listeners.set(elementId, /* @__PURE__ */ new Set());
|
|
750
|
-
}
|
|
751
|
-
this.listeners.get(elementId).add(listener);
|
|
752
|
-
return () => {
|
|
753
|
-
const listeners = this.listeners.get(elementId);
|
|
754
|
-
if (listeners) {
|
|
755
|
-
listeners.delete(listener);
|
|
756
|
-
}
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* 리스너들에게 상태 변경 알림
|
|
761
|
-
*/
|
|
762
|
-
notifyListeners(elementId, state) {
|
|
763
|
-
const listeners = this.listeners.get(elementId);
|
|
764
|
-
if (!listeners) return;
|
|
765
|
-
listeners.forEach((listener) => {
|
|
766
|
-
try {
|
|
767
|
-
listener(state);
|
|
768
|
-
} catch (error) {
|
|
769
|
-
if (process.env.NODE_ENV === "development") {
|
|
770
|
-
console.error(`MotionStateManager listener error for ${elementId}:`, error);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* 모든 상태 초기화
|
|
777
|
-
*/
|
|
778
|
-
reset() {
|
|
779
|
-
this.states.clear();
|
|
780
|
-
this.listeners.clear();
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* 특정 요소 상태 초기화
|
|
784
|
-
*/
|
|
785
|
-
resetElement(elementId) {
|
|
786
|
-
this.states.delete(elementId);
|
|
787
|
-
this.listeners.delete(elementId);
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* 디버그용 상태 출력
|
|
791
|
-
*/
|
|
792
|
-
debug() {
|
|
793
|
-
if (process.env.NODE_ENV === "development") {
|
|
794
|
-
console.log("MotionStateManager Debug:");
|
|
795
|
-
this.states.forEach((state, elementId) => {
|
|
796
|
-
console.log(` ${elementId}:`, {
|
|
797
|
-
internalVisibility: state.internalVisibility,
|
|
798
|
-
triggeredVisibility: state.triggeredVisibility,
|
|
799
|
-
finalVisibility: state.finalVisibility,
|
|
800
|
-
opacity: state.opacity,
|
|
801
|
-
translateY: state.translateY,
|
|
802
|
-
isAnimating: state.isAnimating
|
|
803
|
-
});
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
};
|
|
808
|
-
var motionStateManager = new MotionStateManager();
|
|
809
|
-
|
|
810
|
-
// src/hooks/usePageMotions.ts
|
|
811
111
|
function usePageMotions(config) {
|
|
812
112
|
const [motions, setMotions] = useState(/* @__PURE__ */ new Map());
|
|
813
113
|
const observersRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -1300,155 +600,6 @@ function useSmartMotion(options = {}) {
|
|
|
1300
600
|
};
|
|
1301
601
|
}
|
|
1302
602
|
|
|
1303
|
-
// src/profiles/neutral.ts
|
|
1304
|
-
var neutral = {
|
|
1305
|
-
name: "neutral",
|
|
1306
|
-
base: {
|
|
1307
|
-
duration: 700,
|
|
1308
|
-
easing: "ease-out",
|
|
1309
|
-
threshold: 0.1,
|
|
1310
|
-
triggerOnce: true
|
|
1311
|
-
},
|
|
1312
|
-
entrance: {
|
|
1313
|
-
slide: {
|
|
1314
|
-
distance: 32,
|
|
1315
|
-
easing: "ease-out"
|
|
1316
|
-
},
|
|
1317
|
-
fade: {
|
|
1318
|
-
initialOpacity: 0
|
|
1319
|
-
},
|
|
1320
|
-
scale: {
|
|
1321
|
-
from: 0.95
|
|
1322
|
-
},
|
|
1323
|
-
bounce: {
|
|
1324
|
-
intensity: 0.3,
|
|
1325
|
-
easing: "cubic-bezier(0.34, 1.56, 0.64, 1)"
|
|
1326
|
-
}
|
|
1327
|
-
},
|
|
1328
|
-
stagger: {
|
|
1329
|
-
perItem: 100,
|
|
1330
|
-
baseDelay: 0
|
|
1331
|
-
},
|
|
1332
|
-
interaction: {
|
|
1333
|
-
hover: {
|
|
1334
|
-
scale: 1.05,
|
|
1335
|
-
y: -2,
|
|
1336
|
-
duration: 200,
|
|
1337
|
-
easing: "ease-out"
|
|
1338
|
-
}
|
|
1339
|
-
},
|
|
1340
|
-
spring: {
|
|
1341
|
-
mass: 1,
|
|
1342
|
-
stiffness: 100,
|
|
1343
|
-
damping: 10,
|
|
1344
|
-
restDelta: 0.01,
|
|
1345
|
-
restSpeed: 0.01
|
|
1346
|
-
},
|
|
1347
|
-
reducedMotion: "fade-only"
|
|
1348
|
-
};
|
|
1349
|
-
|
|
1350
|
-
// src/profiles/hua.ts
|
|
1351
|
-
var hua = {
|
|
1352
|
-
name: "hua",
|
|
1353
|
-
base: {
|
|
1354
|
-
duration: 640,
|
|
1355
|
-
easing: "cubic-bezier(0.22, 0.68, 0.35, 1.10)",
|
|
1356
|
-
threshold: 0.12,
|
|
1357
|
-
triggerOnce: true
|
|
1358
|
-
},
|
|
1359
|
-
entrance: {
|
|
1360
|
-
slide: {
|
|
1361
|
-
distance: 28,
|
|
1362
|
-
easing: "cubic-bezier(0.22, 0.68, 0.35, 1.14)"
|
|
1363
|
-
},
|
|
1364
|
-
fade: {
|
|
1365
|
-
initialOpacity: 0
|
|
1366
|
-
},
|
|
1367
|
-
scale: {
|
|
1368
|
-
from: 0.97
|
|
1369
|
-
},
|
|
1370
|
-
bounce: {
|
|
1371
|
-
intensity: 0.2,
|
|
1372
|
-
easing: "cubic-bezier(0.22, 0.68, 0.35, 1.12)"
|
|
1373
|
-
}
|
|
1374
|
-
},
|
|
1375
|
-
stagger: {
|
|
1376
|
-
perItem: 80,
|
|
1377
|
-
baseDelay: 0
|
|
1378
|
-
},
|
|
1379
|
-
interaction: {
|
|
1380
|
-
hover: {
|
|
1381
|
-
scale: 1.008,
|
|
1382
|
-
y: -1,
|
|
1383
|
-
duration: 180,
|
|
1384
|
-
easing: "cubic-bezier(0.22, 0.68, 0.35, 1.10)"
|
|
1385
|
-
}
|
|
1386
|
-
},
|
|
1387
|
-
spring: {
|
|
1388
|
-
mass: 1,
|
|
1389
|
-
stiffness: 180,
|
|
1390
|
-
damping: 18,
|
|
1391
|
-
restDelta: 5e-3,
|
|
1392
|
-
restSpeed: 5e-3
|
|
1393
|
-
},
|
|
1394
|
-
reducedMotion: "fade-only"
|
|
1395
|
-
};
|
|
1396
|
-
|
|
1397
|
-
// src/profiles/index.ts
|
|
1398
|
-
var PROFILES = {
|
|
1399
|
-
neutral,
|
|
1400
|
-
hua
|
|
1401
|
-
};
|
|
1402
|
-
function resolveProfile(profile) {
|
|
1403
|
-
if (typeof profile === "string") {
|
|
1404
|
-
return PROFILES[profile] ?? neutral;
|
|
1405
|
-
}
|
|
1406
|
-
return profile;
|
|
1407
|
-
}
|
|
1408
|
-
function mergeProfileOverrides(base, overrides) {
|
|
1409
|
-
return deepMerge(
|
|
1410
|
-
base,
|
|
1411
|
-
overrides
|
|
1412
|
-
);
|
|
1413
|
-
}
|
|
1414
|
-
function deepMerge(target, source) {
|
|
1415
|
-
const result = { ...target };
|
|
1416
|
-
for (const key of Object.keys(source)) {
|
|
1417
|
-
const sourceVal = source[key];
|
|
1418
|
-
const targetVal = result[key];
|
|
1419
|
-
if (sourceVal !== null && sourceVal !== void 0 && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null && !Array.isArray(targetVal)) {
|
|
1420
|
-
result[key] = deepMerge(
|
|
1421
|
-
targetVal,
|
|
1422
|
-
sourceVal
|
|
1423
|
-
);
|
|
1424
|
-
} else if (sourceVal !== void 0) {
|
|
1425
|
-
result[key] = sourceVal;
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
return result;
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
// src/profiles/MotionProfileContext.ts
|
|
1432
|
-
var MotionProfileContext = createContext(neutral);
|
|
1433
|
-
function MotionProfileProvider({
|
|
1434
|
-
profile = "neutral",
|
|
1435
|
-
overrides,
|
|
1436
|
-
children
|
|
1437
|
-
}) {
|
|
1438
|
-
const resolved = useMemo(() => {
|
|
1439
|
-
const base = resolveProfile(profile);
|
|
1440
|
-
return overrides ? mergeProfileOverrides(base, overrides) : base;
|
|
1441
|
-
}, [profile, overrides]);
|
|
1442
|
-
return createElement(
|
|
1443
|
-
MotionProfileContext.Provider,
|
|
1444
|
-
{ value: resolved },
|
|
1445
|
-
children
|
|
1446
|
-
);
|
|
1447
|
-
}
|
|
1448
|
-
function useMotionProfile() {
|
|
1449
|
-
return useContext(MotionProfileContext);
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
603
|
// src/utils/sharedIntersectionObserver.ts
|
|
1453
604
|
var pool = /* @__PURE__ */ new Map();
|
|
1454
605
|
function makeKey(threshold, rootMargin) {
|
|
@@ -1541,8 +692,8 @@ function getInitialStyle(type, distance) {
|
|
|
1541
692
|
function getVisibleStyle() {
|
|
1542
693
|
return { opacity: 1, transform: "none" };
|
|
1543
694
|
}
|
|
1544
|
-
function getEasingForType(type,
|
|
1545
|
-
if (
|
|
695
|
+
function getEasingForType(type, easing) {
|
|
696
|
+
if (easing) return easing;
|
|
1546
697
|
if (type === "bounceIn") return "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
1547
698
|
return "ease-out";
|
|
1548
699
|
}
|
|
@@ -1608,8 +759,8 @@ function getMultiEffectVisibleStyle(effects) {
|
|
|
1608
759
|
}
|
|
1609
760
|
return style;
|
|
1610
761
|
}
|
|
1611
|
-
function getMultiEffectEasing(effects,
|
|
1612
|
-
if (
|
|
762
|
+
function getMultiEffectEasing(effects, easing) {
|
|
763
|
+
if (easing) return easing;
|
|
1613
764
|
if (effects.bounce) return "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
1614
765
|
return "ease-out";
|
|
1615
766
|
}
|
|
@@ -1621,7 +772,7 @@ function useUnifiedMotion(options) {
|
|
|
1621
772
|
duration = profile.base.duration,
|
|
1622
773
|
autoStart = true,
|
|
1623
774
|
delay = 0,
|
|
1624
|
-
easing
|
|
775
|
+
easing,
|
|
1625
776
|
threshold = profile.base.threshold,
|
|
1626
777
|
triggerOnce = profile.base.triggerOnce,
|
|
1627
778
|
distance = profile.entrance.slide.distance,
|
|
@@ -1631,7 +782,7 @@ function useUnifiedMotion(options) {
|
|
|
1631
782
|
onReset
|
|
1632
783
|
} = options;
|
|
1633
784
|
const resolvedType = type ?? "fadeIn";
|
|
1634
|
-
const resolvedEasing = getEasingForType(resolvedType,
|
|
785
|
+
const resolvedEasing = getEasingForType(resolvedType, easing);
|
|
1635
786
|
const ref = useRef(null);
|
|
1636
787
|
const [isVisible, setIsVisible] = useState(false);
|
|
1637
788
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
@@ -1683,7 +834,7 @@ function useUnifiedMotion(options) {
|
|
|
1683
834
|
const style = useMemo(() => {
|
|
1684
835
|
if (effects) {
|
|
1685
836
|
const base2 = isVisible ? getMultiEffectVisibleStyle(effects) : getMultiEffectInitialStyle(effects, distance);
|
|
1686
|
-
const resolvedEasingMulti = getMultiEffectEasing(effects,
|
|
837
|
+
const resolvedEasingMulti = getMultiEffectEasing(effects, easing);
|
|
1687
838
|
return {
|
|
1688
839
|
...base2,
|
|
1689
840
|
transition: `all ${duration}ms ${resolvedEasingMulti}`,
|
|
@@ -1702,7 +853,7 @@ function useUnifiedMotion(options) {
|
|
|
1702
853
|
"--motion-easing": resolvedEasing,
|
|
1703
854
|
"--motion-progress": `${progress}`
|
|
1704
855
|
};
|
|
1705
|
-
}, [isVisible, type, effects, distance, duration, resolvedEasing,
|
|
856
|
+
}, [isVisible, type, effects, distance, duration, resolvedEasing, easing, delay, progress, resolvedType]);
|
|
1706
857
|
return {
|
|
1707
858
|
ref,
|
|
1708
859
|
isVisible,
|
|
@@ -1721,7 +872,7 @@ function useFadeIn(options = {}) {
|
|
|
1721
872
|
duration = profile.base.duration,
|
|
1722
873
|
threshold = profile.base.threshold,
|
|
1723
874
|
triggerOnce = profile.base.triggerOnce,
|
|
1724
|
-
easing
|
|
875
|
+
easing = profile.base.easing,
|
|
1725
876
|
autoStart = true,
|
|
1726
877
|
initialOpacity = profile.entrance.fade.initialOpacity,
|
|
1727
878
|
targetOpacity = 1,
|
|
@@ -1791,6 +942,10 @@ function useFadeIn(options = {}) {
|
|
|
1791
942
|
setProgress(0);
|
|
1792
943
|
onReset?.();
|
|
1793
944
|
}, [stop, onReset]);
|
|
945
|
+
const pause = useCallback(() => {
|
|
946
|
+
}, []);
|
|
947
|
+
const resume = useCallback(() => {
|
|
948
|
+
}, []);
|
|
1794
949
|
useEffect(() => {
|
|
1795
950
|
if (!ref.current || !autoStart) return;
|
|
1796
951
|
return observeElement(
|
|
@@ -1807,14 +962,25 @@ function useFadeIn(options = {}) {
|
|
|
1807
962
|
stop();
|
|
1808
963
|
};
|
|
1809
964
|
}, [stop]);
|
|
1810
|
-
const style = useMemo(
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
965
|
+
const style = useMemo(
|
|
966
|
+
() => ({
|
|
967
|
+
opacity: isVisible ? targetOpacity : initialOpacity,
|
|
968
|
+
transition: `opacity ${duration}ms ${easing}`,
|
|
969
|
+
"--motion-delay": `${delay}ms`,
|
|
970
|
+
"--motion-duration": `${duration}ms`,
|
|
971
|
+
"--motion-easing": easing,
|
|
972
|
+
"--motion-progress": `${progress}`
|
|
973
|
+
}),
|
|
974
|
+
[
|
|
975
|
+
isVisible,
|
|
976
|
+
targetOpacity,
|
|
977
|
+
initialOpacity,
|
|
978
|
+
duration,
|
|
979
|
+
easing,
|
|
980
|
+
delay,
|
|
981
|
+
progress
|
|
982
|
+
]
|
|
983
|
+
);
|
|
1818
984
|
return {
|
|
1819
985
|
ref,
|
|
1820
986
|
isVisible,
|
|
@@ -1823,7 +989,9 @@ function useFadeIn(options = {}) {
|
|
|
1823
989
|
progress,
|
|
1824
990
|
start,
|
|
1825
991
|
stop,
|
|
1826
|
-
reset
|
|
992
|
+
reset,
|
|
993
|
+
pause,
|
|
994
|
+
resume
|
|
1827
995
|
};
|
|
1828
996
|
}
|
|
1829
997
|
function useSlideUp(options = {}) {
|
|
@@ -1833,7 +1001,7 @@ function useSlideUp(options = {}) {
|
|
|
1833
1001
|
duration = profile.base.duration,
|
|
1834
1002
|
threshold = profile.base.threshold,
|
|
1835
1003
|
triggerOnce = profile.base.triggerOnce,
|
|
1836
|
-
easing
|
|
1004
|
+
easing = profile.entrance.slide.easing,
|
|
1837
1005
|
autoStart = true,
|
|
1838
1006
|
direction = "up",
|
|
1839
1007
|
distance = profile.entrance.slide.distance,
|
|
@@ -1912,6 +1080,10 @@ function useSlideUp(options = {}) {
|
|
|
1912
1080
|
setProgress(0);
|
|
1913
1081
|
onReset?.();
|
|
1914
1082
|
}, [stop, onReset]);
|
|
1083
|
+
const pause = useCallback(() => {
|
|
1084
|
+
}, []);
|
|
1085
|
+
const resume = useCallback(() => {
|
|
1086
|
+
}, []);
|
|
1915
1087
|
useEffect(() => {
|
|
1916
1088
|
if (!ref.current || !autoStart) return;
|
|
1917
1089
|
return observeElement(
|
|
@@ -1928,21 +1100,37 @@ function useSlideUp(options = {}) {
|
|
|
1928
1100
|
stop();
|
|
1929
1101
|
};
|
|
1930
1102
|
}, [stop]);
|
|
1931
|
-
const initialTransform = useMemo(
|
|
1103
|
+
const initialTransform = useMemo(
|
|
1104
|
+
() => getInitialTransform2(),
|
|
1105
|
+
[getInitialTransform2]
|
|
1106
|
+
);
|
|
1932
1107
|
const finalTransform = useMemo(() => {
|
|
1933
1108
|
return direction === "left" || direction === "right" ? "translateX(0)" : "translateY(0)";
|
|
1934
1109
|
}, [direction]);
|
|
1935
|
-
const style = useMemo(
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1110
|
+
const style = useMemo(
|
|
1111
|
+
() => ({
|
|
1112
|
+
opacity: isVisible ? 1 : 0,
|
|
1113
|
+
transform: isVisible ? finalTransform : initialTransform,
|
|
1114
|
+
transition: `opacity ${duration}ms ${easing}, transform ${duration}ms ${easing}`,
|
|
1115
|
+
"--motion-delay": `${delay}ms`,
|
|
1116
|
+
"--motion-duration": `${duration}ms`,
|
|
1117
|
+
"--motion-easing": easing,
|
|
1118
|
+
"--motion-progress": `${progress}`,
|
|
1119
|
+
"--motion-direction": direction,
|
|
1120
|
+
"--motion-distance": `${distance}px`
|
|
1121
|
+
}),
|
|
1122
|
+
[
|
|
1123
|
+
isVisible,
|
|
1124
|
+
initialTransform,
|
|
1125
|
+
finalTransform,
|
|
1126
|
+
duration,
|
|
1127
|
+
easing,
|
|
1128
|
+
delay,
|
|
1129
|
+
progress,
|
|
1130
|
+
direction,
|
|
1131
|
+
distance
|
|
1132
|
+
]
|
|
1133
|
+
);
|
|
1946
1134
|
return {
|
|
1947
1135
|
ref,
|
|
1948
1136
|
isVisible,
|
|
@@ -1951,7 +1139,9 @@ function useSlideUp(options = {}) {
|
|
|
1951
1139
|
progress,
|
|
1952
1140
|
start,
|
|
1953
1141
|
stop,
|
|
1954
|
-
reset
|
|
1142
|
+
reset,
|
|
1143
|
+
pause,
|
|
1144
|
+
resume
|
|
1955
1145
|
};
|
|
1956
1146
|
}
|
|
1957
1147
|
|
|
@@ -1972,7 +1162,7 @@ function useScaleIn(options = {}) {
|
|
|
1972
1162
|
duration = profile.base.duration,
|
|
1973
1163
|
delay = 0,
|
|
1974
1164
|
autoStart = true,
|
|
1975
|
-
easing
|
|
1165
|
+
easing = profile.base.easing,
|
|
1976
1166
|
threshold = profile.base.threshold,
|
|
1977
1167
|
triggerOnce = profile.base.triggerOnce,
|
|
1978
1168
|
onComplete,
|
|
@@ -2031,6 +1221,10 @@ function useScaleIn(options = {}) {
|
|
|
2031
1221
|
}
|
|
2032
1222
|
onReset?.();
|
|
2033
1223
|
}, [stop, initialScale, onReset]);
|
|
1224
|
+
const pause = useCallback(() => {
|
|
1225
|
+
}, []);
|
|
1226
|
+
const resume = useCallback(() => {
|
|
1227
|
+
}, []);
|
|
2034
1228
|
useEffect(() => {
|
|
2035
1229
|
if (!ref.current || !autoStart) return;
|
|
2036
1230
|
return observeElement(
|
|
@@ -2047,15 +1241,18 @@ function useScaleIn(options = {}) {
|
|
|
2047
1241
|
stop();
|
|
2048
1242
|
};
|
|
2049
1243
|
}, [stop]);
|
|
2050
|
-
const style = useMemo(
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
1244
|
+
const style = useMemo(
|
|
1245
|
+
() => ({
|
|
1246
|
+
transform: `scale(${scale})`,
|
|
1247
|
+
opacity,
|
|
1248
|
+
transition: `all ${duration}ms ${easing}`,
|
|
1249
|
+
"--motion-delay": `${delay}ms`,
|
|
1250
|
+
"--motion-duration": `${duration}ms`,
|
|
1251
|
+
"--motion-easing": easing,
|
|
1252
|
+
"--motion-progress": `${progress}`
|
|
1253
|
+
}),
|
|
1254
|
+
[scale, opacity, duration, easing, delay, progress]
|
|
1255
|
+
);
|
|
2059
1256
|
return {
|
|
2060
1257
|
ref,
|
|
2061
1258
|
isVisible,
|
|
@@ -2064,7 +1261,9 @@ function useScaleIn(options = {}) {
|
|
|
2064
1261
|
progress,
|
|
2065
1262
|
start,
|
|
2066
1263
|
reset,
|
|
2067
|
-
stop
|
|
1264
|
+
stop,
|
|
1265
|
+
pause,
|
|
1266
|
+
resume
|
|
2068
1267
|
};
|
|
2069
1268
|
}
|
|
2070
1269
|
function useBounceIn(options = {}) {
|
|
@@ -2076,7 +1275,7 @@ function useBounceIn(options = {}) {
|
|
|
2076
1275
|
intensity = profile.entrance.bounce.intensity,
|
|
2077
1276
|
threshold = profile.base.threshold,
|
|
2078
1277
|
triggerOnce = profile.base.triggerOnce,
|
|
2079
|
-
easing
|
|
1278
|
+
easing = profile.entrance.bounce.easing,
|
|
2080
1279
|
onComplete,
|
|
2081
1280
|
onStart,
|
|
2082
1281
|
onStop,
|
|
@@ -2142,6 +1341,10 @@ function useBounceIn(options = {}) {
|
|
|
2142
1341
|
}
|
|
2143
1342
|
onReset?.();
|
|
2144
1343
|
}, [stop, onReset]);
|
|
1344
|
+
const pause = useCallback(() => {
|
|
1345
|
+
}, []);
|
|
1346
|
+
const resume = useCallback(() => {
|
|
1347
|
+
}, []);
|
|
2145
1348
|
useEffect(() => {
|
|
2146
1349
|
if (!ref.current || !autoStart) return;
|
|
2147
1350
|
return observeElement(
|
|
@@ -2158,15 +1361,18 @@ function useBounceIn(options = {}) {
|
|
|
2158
1361
|
stop();
|
|
2159
1362
|
};
|
|
2160
1363
|
}, [stop]);
|
|
2161
|
-
const style = useMemo(
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
1364
|
+
const style = useMemo(
|
|
1365
|
+
() => ({
|
|
1366
|
+
transform: `scale(${scale})`,
|
|
1367
|
+
opacity,
|
|
1368
|
+
transition: `all ${duration}ms ${easing}`,
|
|
1369
|
+
"--motion-delay": `${delay}ms`,
|
|
1370
|
+
"--motion-duration": `${duration}ms`,
|
|
1371
|
+
"--motion-easing": easing,
|
|
1372
|
+
"--motion-progress": `${progress}`
|
|
1373
|
+
}),
|
|
1374
|
+
[scale, opacity, duration, easing, delay, progress]
|
|
1375
|
+
);
|
|
2170
1376
|
return {
|
|
2171
1377
|
ref,
|
|
2172
1378
|
isVisible,
|
|
@@ -2175,204 +1381,11 @@ function useBounceIn(options = {}) {
|
|
|
2175
1381
|
progress,
|
|
2176
1382
|
start,
|
|
2177
1383
|
reset,
|
|
2178
|
-
stop
|
|
1384
|
+
stop,
|
|
1385
|
+
pause,
|
|
1386
|
+
resume
|
|
2179
1387
|
};
|
|
2180
1388
|
}
|
|
2181
|
-
|
|
2182
|
-
// src/utils/easing.ts
|
|
2183
|
-
var linear = (t) => t;
|
|
2184
|
-
var easeIn = (t) => t * t;
|
|
2185
|
-
var easeOut = (t) => 1 - (1 - t) * (1 - t);
|
|
2186
|
-
var easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
2187
|
-
var easeInQuad = (t) => t * t;
|
|
2188
|
-
var easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
|
|
2189
|
-
var easeInOutQuad = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
2190
|
-
var easeInCubic = (t) => t * t * t;
|
|
2191
|
-
var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
2192
|
-
var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
2193
|
-
var easeInQuart = (t) => t * t * t * t;
|
|
2194
|
-
var easeOutQuart = (t) => 1 - Math.pow(1 - t, 4);
|
|
2195
|
-
var easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
|
|
2196
|
-
var easeInQuint = (t) => t * t * t * t * t;
|
|
2197
|
-
var easeOutQuint = (t) => 1 - Math.pow(1 - t, 5);
|
|
2198
|
-
var easeInOutQuint = (t) => t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
|
|
2199
|
-
var easeInSine = (t) => 1 - Math.cos(t * Math.PI / 2);
|
|
2200
|
-
var easeOutSine = (t) => Math.sin(t * Math.PI / 2);
|
|
2201
|
-
var easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2;
|
|
2202
|
-
var easeInExpo = (t) => t === 0 ? 0 : Math.pow(2, 10 * t - 10);
|
|
2203
|
-
var easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
|
2204
|
-
var easeInOutExpo = (t) => {
|
|
2205
|
-
if (t === 0) return 0;
|
|
2206
|
-
if (t === 1) return 1;
|
|
2207
|
-
if (t < 0.5) return Math.pow(2, 20 * t - 10) / 2;
|
|
2208
|
-
return (2 - Math.pow(2, -20 * t + 10)) / 2;
|
|
2209
|
-
};
|
|
2210
|
-
var easeInCirc = (t) => 1 - Math.sqrt(1 - Math.pow(t, 2));
|
|
2211
|
-
var easeOutCirc = (t) => Math.sqrt(1 - Math.pow(t - 1, 2));
|
|
2212
|
-
var easeInOutCirc = (t) => {
|
|
2213
|
-
if (t < 0.5) return (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2;
|
|
2214
|
-
return (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
|
|
2215
|
-
};
|
|
2216
|
-
var easeInBounce = (t) => 1 - easeOutBounce(1 - t);
|
|
2217
|
-
var easeOutBounce = (t) => {
|
|
2218
|
-
const n1 = 7.5625;
|
|
2219
|
-
const d1 = 2.75;
|
|
2220
|
-
if (t < 1 / d1) {
|
|
2221
|
-
return n1 * t * t;
|
|
2222
|
-
} else if (t < 2 / d1) {
|
|
2223
|
-
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
2224
|
-
} else if (t < 2.5 / d1) {
|
|
2225
|
-
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
2226
|
-
} else {
|
|
2227
|
-
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
2228
|
-
}
|
|
2229
|
-
};
|
|
2230
|
-
var easeInOutBounce = (t) => {
|
|
2231
|
-
if (t < 0.5) return (1 - easeOutBounce(1 - 2 * t)) / 2;
|
|
2232
|
-
return (1 + easeOutBounce(2 * t - 1)) / 2;
|
|
2233
|
-
};
|
|
2234
|
-
var easeInBack = (t) => {
|
|
2235
|
-
const c1 = 1.70158;
|
|
2236
|
-
const c3 = c1 + 1;
|
|
2237
|
-
return c3 * t * t * t - c1 * t * t;
|
|
2238
|
-
};
|
|
2239
|
-
var easeOutBack = (t) => {
|
|
2240
|
-
const c1 = 1.70158;
|
|
2241
|
-
const c3 = c1 + 1;
|
|
2242
|
-
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
2243
|
-
};
|
|
2244
|
-
var easeInOutBack = (t) => {
|
|
2245
|
-
const c1 = 1.70158;
|
|
2246
|
-
const c2 = c1 * 1.525;
|
|
2247
|
-
if (t < 0.5) {
|
|
2248
|
-
return Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2) / 2;
|
|
2249
|
-
} else {
|
|
2250
|
-
return (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
|
2251
|
-
}
|
|
2252
|
-
};
|
|
2253
|
-
var easeInElastic = (t) => {
|
|
2254
|
-
const c4 = 2 * Math.PI / 3;
|
|
2255
|
-
if (t === 0) return 0;
|
|
2256
|
-
if (t === 1) return 1;
|
|
2257
|
-
return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 0.75) * c4);
|
|
2258
|
-
};
|
|
2259
|
-
var easeOutElastic = (t) => {
|
|
2260
|
-
const c4 = 2 * Math.PI / 3;
|
|
2261
|
-
if (t === 0) return 0;
|
|
2262
|
-
if (t === 1) return 1;
|
|
2263
|
-
return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
2264
|
-
};
|
|
2265
|
-
var easeInOutElastic = (t) => {
|
|
2266
|
-
const c5 = 2 * Math.PI / 4.5;
|
|
2267
|
-
if (t === 0) return 0;
|
|
2268
|
-
if (t === 1) return 1;
|
|
2269
|
-
if (t < 0.5) {
|
|
2270
|
-
return -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2;
|
|
2271
|
-
} else {
|
|
2272
|
-
return Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5) / 2 + 1;
|
|
2273
|
-
}
|
|
2274
|
-
};
|
|
2275
|
-
var pulse = (t) => Math.sin(t * Math.PI) * 0.5 + 0.5;
|
|
2276
|
-
var pulseSmooth = (t) => Math.sin(t * Math.PI * 2) * 0.3 + 0.7;
|
|
2277
|
-
var skeletonWave = (t) => Math.sin((t - 0.5) * Math.PI * 2) * 0.5 + 0.5;
|
|
2278
|
-
var blink = (t) => t < 0.5 ? 1 : 0;
|
|
2279
|
-
var easing = {
|
|
2280
|
-
linear,
|
|
2281
|
-
easeIn,
|
|
2282
|
-
easeOut,
|
|
2283
|
-
easeInOut,
|
|
2284
|
-
easeInQuad,
|
|
2285
|
-
easeOutQuad,
|
|
2286
|
-
easeInOutQuad,
|
|
2287
|
-
easeInCubic,
|
|
2288
|
-
easeOutCubic,
|
|
2289
|
-
easeInOutCubic,
|
|
2290
|
-
easeInQuart,
|
|
2291
|
-
easeOutQuart,
|
|
2292
|
-
easeInOutQuart,
|
|
2293
|
-
easeInQuint,
|
|
2294
|
-
easeOutQuint,
|
|
2295
|
-
easeInOutQuint,
|
|
2296
|
-
easeInSine,
|
|
2297
|
-
easeOutSine,
|
|
2298
|
-
easeInOutSine,
|
|
2299
|
-
easeInExpo,
|
|
2300
|
-
easeOutExpo,
|
|
2301
|
-
easeInOutExpo,
|
|
2302
|
-
easeInCirc,
|
|
2303
|
-
easeOutCirc,
|
|
2304
|
-
easeInOutCirc,
|
|
2305
|
-
easeInBounce,
|
|
2306
|
-
easeOutBounce,
|
|
2307
|
-
easeInOutBounce,
|
|
2308
|
-
easeInBack,
|
|
2309
|
-
easeOutBack,
|
|
2310
|
-
easeInOutBack,
|
|
2311
|
-
easeInElastic,
|
|
2312
|
-
easeOutElastic,
|
|
2313
|
-
easeInOutElastic,
|
|
2314
|
-
pulse,
|
|
2315
|
-
pulseSmooth,
|
|
2316
|
-
skeletonWave,
|
|
2317
|
-
blink
|
|
2318
|
-
};
|
|
2319
|
-
function isValidEasing(easingName) {
|
|
2320
|
-
return easingName in easing;
|
|
2321
|
-
}
|
|
2322
|
-
function getEasing(easingName) {
|
|
2323
|
-
if (typeof easingName === "function") {
|
|
2324
|
-
return easingName;
|
|
2325
|
-
}
|
|
2326
|
-
if (typeof easingName === "string") {
|
|
2327
|
-
if (easingName in easing) {
|
|
2328
|
-
return easing[easingName];
|
|
2329
|
-
}
|
|
2330
|
-
if (easingName === "bounce") {
|
|
2331
|
-
return easing.easeOutBounce;
|
|
2332
|
-
}
|
|
2333
|
-
if (process.env.NODE_ENV === "development") {
|
|
2334
|
-
console.warn(`[HUA Motion] Unknown easing "${easingName}", using default "easeOut".`);
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
return easeOut;
|
|
2338
|
-
}
|
|
2339
|
-
function applyEasing(t, easingName) {
|
|
2340
|
-
const easingFn = getEasing(easingName);
|
|
2341
|
-
return easingFn(t);
|
|
2342
|
-
}
|
|
2343
|
-
function safeApplyEasing(t, easingName) {
|
|
2344
|
-
try {
|
|
2345
|
-
const easingFn = getEasing(easingName);
|
|
2346
|
-
return easingFn(t);
|
|
2347
|
-
} catch (err) {
|
|
2348
|
-
if (process.env.NODE_ENV === "development") {
|
|
2349
|
-
console.error(`[HUA Motion] Failed to apply easing "${easingName}":`, err);
|
|
2350
|
-
}
|
|
2351
|
-
return easeOut(t);
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
function getAvailableEasings() {
|
|
2355
|
-
return Object.keys(easing);
|
|
2356
|
-
}
|
|
2357
|
-
function isEasingFunction(value) {
|
|
2358
|
-
return typeof value === "function";
|
|
2359
|
-
}
|
|
2360
|
-
var easingPresets = {
|
|
2361
|
-
default: "easeOut",
|
|
2362
|
-
smooth: "easeInOutCubic",
|
|
2363
|
-
fast: "easeOutQuad",
|
|
2364
|
-
slow: "easeInOutSine",
|
|
2365
|
-
bouncy: "easeOutBounce",
|
|
2366
|
-
elastic: "easeOutElastic",
|
|
2367
|
-
fade: "easeInOut",
|
|
2368
|
-
scale: "easeOutBack"
|
|
2369
|
-
};
|
|
2370
|
-
function getPresetEasing(preset) {
|
|
2371
|
-
const easingName = easingPresets[preset];
|
|
2372
|
-
return getEasing(easingName);
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
// src/hooks/usePulse.ts
|
|
2376
1389
|
function usePulse(options = {}) {
|
|
2377
1390
|
const {
|
|
2378
1391
|
duration = 3e3,
|
|
@@ -2476,20 +1489,6 @@ function usePulse(options = {}) {
|
|
|
2476
1489
|
reset
|
|
2477
1490
|
};
|
|
2478
1491
|
}
|
|
2479
|
-
|
|
2480
|
-
// src/utils/springPhysics.ts
|
|
2481
|
-
function calculateSpring(currentValue, currentVelocity, targetValue, deltaTime, config) {
|
|
2482
|
-
const { stiffness, damping, mass } = config;
|
|
2483
|
-
const displacement = currentValue - targetValue;
|
|
2484
|
-
const springForce = -stiffness * displacement;
|
|
2485
|
-
const dampingForce = -damping * currentVelocity;
|
|
2486
|
-
const acceleration = (springForce + dampingForce) / mass;
|
|
2487
|
-
const newVelocity = currentVelocity + acceleration * deltaTime;
|
|
2488
|
-
const newValue = currentValue + newVelocity * deltaTime;
|
|
2489
|
-
return { value: newValue, velocity: newVelocity };
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
// src/hooks/useSpringMotion.ts
|
|
2493
1492
|
function useSpringMotion(options) {
|
|
2494
1493
|
const profile = useMotionProfile();
|
|
2495
1494
|
const {
|
|
@@ -2629,7 +1628,7 @@ function useGradient(options = {}) {
|
|
|
2629
1628
|
duration = 6e3,
|
|
2630
1629
|
direction = "diagonal",
|
|
2631
1630
|
size = 300,
|
|
2632
|
-
easing
|
|
1631
|
+
easing = "ease-in-out",
|
|
2633
1632
|
autoStart = false
|
|
2634
1633
|
} = options;
|
|
2635
1634
|
const ref = useRef(null);
|
|
@@ -2646,10 +1645,10 @@ function useGradient(options = {}) {
|
|
|
2646
1645
|
return {
|
|
2647
1646
|
background,
|
|
2648
1647
|
backgroundSize,
|
|
2649
|
-
animation: isAnimating ? `gradientShift ${duration}ms ${
|
|
1648
|
+
animation: isAnimating ? `gradientShift ${duration}ms ${easing} infinite` : "none",
|
|
2650
1649
|
backgroundPosition: isAnimating ? void 0 : `${motionProgress}% 50%`
|
|
2651
1650
|
};
|
|
2652
|
-
}, [colors, direction, size, duration,
|
|
1651
|
+
}, [colors, direction, size, duration, easing, isAnimating, motionProgress]);
|
|
2653
1652
|
const start = useCallback(() => {
|
|
2654
1653
|
setIsAnimating(true);
|
|
2655
1654
|
}, []);
|
|
@@ -2697,7 +1696,7 @@ function useHoverMotion(options = {}) {
|
|
|
2697
1696
|
const profile = useMotionProfile();
|
|
2698
1697
|
const {
|
|
2699
1698
|
duration = profile.interaction.hover.duration,
|
|
2700
|
-
easing
|
|
1699
|
+
easing = profile.interaction.hover.easing,
|
|
2701
1700
|
hoverScale = profile.interaction.hover.scale,
|
|
2702
1701
|
hoverY = profile.interaction.hover.y,
|
|
2703
1702
|
hoverOpacity = 1
|
|
@@ -2731,8 +1730,8 @@ function useHoverMotion(options = {}) {
|
|
|
2731
1730
|
const style = useMemo(() => ({
|
|
2732
1731
|
transform: isHovered ? `scale(${hoverScale}) translateY(${hoverY}px)` : "scale(1) translateY(0px)",
|
|
2733
1732
|
opacity: isHovered ? hoverOpacity : 1,
|
|
2734
|
-
transition: `transform ${duration}ms ${
|
|
2735
|
-
}), [isHovered, hoverScale, hoverY, hoverOpacity, duration,
|
|
1733
|
+
transition: `transform ${duration}ms ${easing}, opacity ${duration}ms ${easing}`
|
|
1734
|
+
}), [isHovered, hoverScale, hoverY, hoverOpacity, duration, easing]);
|
|
2736
1735
|
const start = useCallback(() => {
|
|
2737
1736
|
setIsHovered(true);
|
|
2738
1737
|
setIsAnimating(true);
|
|
@@ -2990,7 +1989,7 @@ function useScrollReveal(options = {}) {
|
|
|
2990
1989
|
triggerOnce = profile.base.triggerOnce,
|
|
2991
1990
|
delay = 0,
|
|
2992
1991
|
duration = profile.base.duration,
|
|
2993
|
-
easing
|
|
1992
|
+
easing = profile.base.easing,
|
|
2994
1993
|
motionType = "fadeIn",
|
|
2995
1994
|
onComplete,
|
|
2996
1995
|
onStart,
|
|
@@ -3028,7 +2027,7 @@ function useScrollReveal(options = {}) {
|
|
|
3028
2027
|
const slideDistance = profile.entrance.slide.distance;
|
|
3029
2028
|
const scaleFrom = profile.entrance.scale.from;
|
|
3030
2029
|
const style = useMemo(() => {
|
|
3031
|
-
const baseTransition = `all ${duration}ms ${
|
|
2030
|
+
const baseTransition = `all ${duration}ms ${easing}`;
|
|
3032
2031
|
if (!isVisible) {
|
|
3033
2032
|
switch (motionType) {
|
|
3034
2033
|
case "fadeIn":
|
|
@@ -3078,7 +2077,7 @@ function useScrollReveal(options = {}) {
|
|
|
3078
2077
|
transform: "none",
|
|
3079
2078
|
transition: baseTransition
|
|
3080
2079
|
};
|
|
3081
|
-
}, [isVisible, motionType, duration,
|
|
2080
|
+
}, [isVisible, motionType, duration, easing, slideDistance, scaleFrom]);
|
|
3082
2081
|
const start = useCallback(() => {
|
|
3083
2082
|
setIsAnimating(true);
|
|
3084
2083
|
onStart?.();
|
|
@@ -3300,14 +2299,7 @@ function useMotionState(options = {}) {
|
|
|
3300
2299
|
const seek = useCallback((targetProgress) => {
|
|
3301
2300
|
const clampedProgress = Math.max(0, Math.min(100, targetProgress));
|
|
3302
2301
|
setProgress(clampedProgress);
|
|
3303
|
-
|
|
3304
|
-
if (currentDirection === "reverse") {
|
|
3305
|
-
targetElapsed = (100 - clampedProgress) / 100 * duration;
|
|
3306
|
-
} else if (currentDirection === "alternate") {
|
|
3307
|
-
targetElapsed = clampedProgress / 100 * duration;
|
|
3308
|
-
} else {
|
|
3309
|
-
targetElapsed = clampedProgress / 100 * duration;
|
|
3310
|
-
}
|
|
2302
|
+
const targetElapsed = currentDirection === "reverse" ? (100 - clampedProgress) / 100 * duration : clampedProgress / 100 * duration;
|
|
3311
2303
|
setElapsed(targetElapsed);
|
|
3312
2304
|
if (state === "playing" && startTimeRef.current) {
|
|
3313
2305
|
const currentTime = performance.now();
|
|
@@ -3476,7 +2468,7 @@ function useRepeat(options = {}) {
|
|
|
3476
2468
|
};
|
|
3477
2469
|
}
|
|
3478
2470
|
function useToggleMotion(options = {}) {
|
|
3479
|
-
const { duration = 300, delay = 0, easing
|
|
2471
|
+
const { duration = 300, delay = 0, easing = "ease-in-out" } = options;
|
|
3480
2472
|
const ref = useRef(null);
|
|
3481
2473
|
const [isVisible, setIsVisible] = useState(false);
|
|
3482
2474
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
@@ -3506,8 +2498,8 @@ function useToggleMotion(options = {}) {
|
|
|
3506
2498
|
const style = useMemo(() => ({
|
|
3507
2499
|
opacity: isVisible ? 1 : 0,
|
|
3508
2500
|
transform: isVisible ? "translateY(0) scale(1)" : "translateY(10px) scale(0.95)",
|
|
3509
|
-
transition: `opacity ${duration}ms ${
|
|
3510
|
-
}), [isVisible, duration,
|
|
2501
|
+
transition: `opacity ${duration}ms ${easing} ${delay}ms, transform ${duration}ms ${easing} ${delay}ms`
|
|
2502
|
+
}), [isVisible, duration, easing, delay]);
|
|
3511
2503
|
return {
|
|
3512
2504
|
ref,
|
|
3513
2505
|
isVisible,
|
|
@@ -3655,9 +2647,11 @@ function useReducedMotion() {
|
|
|
3655
2647
|
mediaQuery.removeEventListener("change", handleChange);
|
|
3656
2648
|
};
|
|
3657
2649
|
}, []);
|
|
3658
|
-
return
|
|
3659
|
-
|
|
3660
|
-
|
|
2650
|
+
return prefersReducedMotion;
|
|
2651
|
+
}
|
|
2652
|
+
function useReducedMotionObject() {
|
|
2653
|
+
const prefersReducedMotion = useReducedMotion();
|
|
2654
|
+
return { prefersReducedMotion };
|
|
3661
2655
|
}
|
|
3662
2656
|
function useWindowSize(options = {}) {
|
|
3663
2657
|
const {
|
|
@@ -3978,7 +2972,7 @@ function useGestureMotion(options) {
|
|
|
3978
2972
|
const {
|
|
3979
2973
|
gestureType,
|
|
3980
2974
|
duration = 300,
|
|
3981
|
-
easing
|
|
2975
|
+
easing = "ease-out",
|
|
3982
2976
|
sensitivity = 1,
|
|
3983
2977
|
enabled = true,
|
|
3984
2978
|
onGestureStart,
|
|
@@ -4020,10 +3014,10 @@ function useGestureMotion(options) {
|
|
|
4020
3014
|
}
|
|
4021
3015
|
setMotionStyle({
|
|
4022
3016
|
transform,
|
|
4023
|
-
transition: isActive ? "none" : `all ${duration}ms ${
|
|
3017
|
+
transition: isActive ? "none" : `all ${duration}ms ${easing}`,
|
|
4024
3018
|
cursor: gestureType === "drag" && isActive ? "grabbing" : "pointer"
|
|
4025
3019
|
});
|
|
4026
|
-
}, [gestureState, gestureType, enabled, duration,
|
|
3020
|
+
}, [gestureState, gestureType, enabled, duration, easing, sensitivity]);
|
|
4027
3021
|
const handleMouseDown = useCallback((e) => {
|
|
4028
3022
|
if (!enabled || gestureType !== "drag") return;
|
|
4029
3023
|
isDragging.current = true;
|
|
@@ -4122,7 +3116,7 @@ function useGestureMotion(options) {
|
|
|
4122
3116
|
function useButtonEffect(options = {}) {
|
|
4123
3117
|
const {
|
|
4124
3118
|
duration = 200,
|
|
4125
|
-
easing
|
|
3119
|
+
easing = "ease-out",
|
|
4126
3120
|
type = "scale",
|
|
4127
3121
|
scaleAmount = 0.95,
|
|
4128
3122
|
rippleColor = "rgba(255, 255, 255, 0.6)",
|
|
@@ -4396,7 +3390,7 @@ function useButtonEffect(options = {}) {
|
|
|
4396
3390
|
`,
|
|
4397
3391
|
boxShadow,
|
|
4398
3392
|
opacity: disabled ? disabledOpacity : 1,
|
|
4399
|
-
transition: `all ${duration}ms ${
|
|
3393
|
+
transition: `all ${duration}ms ${easing}`,
|
|
4400
3394
|
willChange: "transform, box-shadow, opacity",
|
|
4401
3395
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
4402
3396
|
position: "relative",
|
|
@@ -4433,7 +3427,7 @@ function useButtonEffect(options = {}) {
|
|
|
4433
3427
|
function useVisibilityToggle(options = {}) {
|
|
4434
3428
|
const {
|
|
4435
3429
|
duration = 300,
|
|
4436
|
-
easing
|
|
3430
|
+
easing = "ease-out",
|
|
4437
3431
|
showScale = 1,
|
|
4438
3432
|
showOpacity = 1,
|
|
4439
3433
|
showRotate = 0,
|
|
@@ -4521,7 +3515,7 @@ function useVisibilityToggle(options = {}) {
|
|
|
4521
3515
|
translate(${isVisible ? showTranslateX : hideTranslateX}px, ${isVisible ? showTranslateY : hideTranslateY}px)
|
|
4522
3516
|
`,
|
|
4523
3517
|
opacity: isVisible ? showOpacity : hideOpacity,
|
|
4524
|
-
transition: `all ${duration}ms ${
|
|
3518
|
+
transition: `all ${duration}ms ${easing}`,
|
|
4525
3519
|
willChange: "transform, opacity"
|
|
4526
3520
|
};
|
|
4527
3521
|
return {
|
|
@@ -4544,7 +3538,7 @@ function useVisibilityToggle(options = {}) {
|
|
|
4544
3538
|
function useScrollToggle(options = {}) {
|
|
4545
3539
|
const {
|
|
4546
3540
|
duration = 300,
|
|
4547
|
-
easing
|
|
3541
|
+
easing = "ease-out",
|
|
4548
3542
|
showScale = 1,
|
|
4549
3543
|
showOpacity = 1,
|
|
4550
3544
|
showRotate = 0,
|
|
@@ -4653,7 +3647,7 @@ function useScrollToggle(options = {}) {
|
|
|
4653
3647
|
translate(${isVisible ? showTranslateX : hideTranslateX}px, ${isVisible ? showTranslateY : hideTranslateY}px)
|
|
4654
3648
|
`,
|
|
4655
3649
|
opacity: isVisible ? showOpacity : hideOpacity,
|
|
4656
|
-
transition: `all ${duration}ms ${
|
|
3650
|
+
transition: `all ${duration}ms ${easing}`,
|
|
4657
3651
|
willChange: "transform, opacity"
|
|
4658
3652
|
};
|
|
4659
3653
|
return {
|
|
@@ -4672,7 +3666,7 @@ function useScrollToggle(options = {}) {
|
|
|
4672
3666
|
function useCardList(options = {}) {
|
|
4673
3667
|
const {
|
|
4674
3668
|
duration = 500,
|
|
4675
|
-
easing
|
|
3669
|
+
easing = "ease-out",
|
|
4676
3670
|
staggerDelay = 100,
|
|
4677
3671
|
cardScale = 1,
|
|
4678
3672
|
cardOpacity = 1,
|
|
@@ -4714,7 +3708,7 @@ function useCardList(options = {}) {
|
|
|
4714
3708
|
translate(${isCardVisible ? cardTranslateX : initialTranslateX}px, ${isCardVisible ? cardTranslateY : initialTranslateY}px)
|
|
4715
3709
|
`,
|
|
4716
3710
|
opacity: isCardVisible ? cardOpacity : initialOpacity,
|
|
4717
|
-
transition: `all ${duration}ms ${
|
|
3711
|
+
transition: `all ${duration}ms ${easing} ${delay}ms`,
|
|
4718
3712
|
willChange: "transform, opacity"
|
|
4719
3713
|
};
|
|
4720
3714
|
});
|
|
@@ -4793,7 +3787,7 @@ function useCardList(options = {}) {
|
|
|
4793
3787
|
function useLoadingSpinner(options = {}) {
|
|
4794
3788
|
const {
|
|
4795
3789
|
duration = 1e3,
|
|
4796
|
-
easing
|
|
3790
|
+
easing = "linear",
|
|
4797
3791
|
type = "rotate",
|
|
4798
3792
|
rotationSpeed = 1,
|
|
4799
3793
|
pulseSpeed = 1,
|
|
@@ -4957,7 +3951,7 @@ function useLoadingSpinner(options = {}) {
|
|
|
4957
3951
|
borderTop: `${thickness}px solid ${color}`,
|
|
4958
3952
|
borderRadius: "50%",
|
|
4959
3953
|
transform: `rotate(${rotationAngle}deg)`,
|
|
4960
|
-
transition: `transform ${duration}ms ${
|
|
3954
|
+
transition: `transform ${duration}ms ${easing}`
|
|
4961
3955
|
};
|
|
4962
3956
|
case "pulse":
|
|
4963
3957
|
return {
|
|
@@ -4965,7 +3959,7 @@ function useLoadingSpinner(options = {}) {
|
|
|
4965
3959
|
backgroundColor: color,
|
|
4966
3960
|
borderRadius: "50%",
|
|
4967
3961
|
transform: `scale(${pulseScale})`,
|
|
4968
|
-
transition: `transform ${duration}ms ${
|
|
3962
|
+
transition: `transform ${duration}ms ${easing}`
|
|
4969
3963
|
};
|
|
4970
3964
|
case "bounce":
|
|
4971
3965
|
return {
|
|
@@ -4973,7 +3967,7 @@ function useLoadingSpinner(options = {}) {
|
|
|
4973
3967
|
backgroundColor: color,
|
|
4974
3968
|
borderRadius: "50%",
|
|
4975
3969
|
transform: `translateY(${bounceOffset}px)`,
|
|
4976
|
-
transition: `transform ${duration}ms ${
|
|
3970
|
+
transition: `transform ${duration}ms ${easing}`
|
|
4977
3971
|
};
|
|
4978
3972
|
case "wave":
|
|
4979
3973
|
return {
|
|
@@ -5028,7 +4022,7 @@ function useLoadingSpinner(options = {}) {
|
|
|
5028
4022
|
function useNavigation(options = {}) {
|
|
5029
4023
|
const {
|
|
5030
4024
|
duration = 300,
|
|
5031
|
-
easing
|
|
4025
|
+
easing = "ease-out",
|
|
5032
4026
|
type = "slide",
|
|
5033
4027
|
slideDirection = "left",
|
|
5034
4028
|
staggerDelay = 50,
|
|
@@ -5184,14 +4178,14 @@ function useNavigation(options = {}) {
|
|
|
5184
4178
|
translate(${translateX}px, ${translateY}px)
|
|
5185
4179
|
`,
|
|
5186
4180
|
opacity,
|
|
5187
|
-
transition: `all ${duration}ms ${
|
|
4181
|
+
transition: `all ${duration}ms ${easing} ${delay}ms`,
|
|
5188
4182
|
willChange: "transform, opacity",
|
|
5189
4183
|
cursor: "pointer"
|
|
5190
4184
|
};
|
|
5191
4185
|
});
|
|
5192
4186
|
const getNavigationStyle = () => {
|
|
5193
4187
|
const baseStyle = {
|
|
5194
|
-
transition: `all ${duration}ms ${
|
|
4188
|
+
transition: `all ${duration}ms ${easing}`,
|
|
5195
4189
|
willChange: "transform, opacity"
|
|
5196
4190
|
};
|
|
5197
4191
|
switch (type) {
|
|
@@ -5245,7 +4239,7 @@ function useSkeleton(options = {}) {
|
|
|
5245
4239
|
const {
|
|
5246
4240
|
delay = 0,
|
|
5247
4241
|
duration = 1500,
|
|
5248
|
-
easing
|
|
4242
|
+
easing = "ease-in-out",
|
|
5249
4243
|
autoStart = true,
|
|
5250
4244
|
backgroundColor = "#f0f0f0",
|
|
5251
4245
|
highlightColor = "#e0e0e0",
|
|
@@ -5254,7 +4248,7 @@ function useSkeleton(options = {}) {
|
|
|
5254
4248
|
width = "100%",
|
|
5255
4249
|
borderRadius = 4,
|
|
5256
4250
|
wave = true,
|
|
5257
|
-
pulse
|
|
4251
|
+
pulse = false,
|
|
5258
4252
|
onComplete,
|
|
5259
4253
|
onStart,
|
|
5260
4254
|
onStop,
|
|
@@ -5337,13 +4331,13 @@ function useSkeleton(options = {}) {
|
|
|
5337
4331
|
position: "relative",
|
|
5338
4332
|
overflow: "hidden",
|
|
5339
4333
|
opacity: isVisible ? 1 : 0,
|
|
5340
|
-
transition: `opacity ${duration}ms ${
|
|
4334
|
+
transition: `opacity ${duration}ms ${easing}`
|
|
5341
4335
|
};
|
|
5342
4336
|
if (wave && isAnimating) {
|
|
5343
4337
|
baseStyle.background = `linear-gradient(90deg, ${backgroundColor} 25%, ${highlightColor} 50%, ${backgroundColor} 75%)`;
|
|
5344
4338
|
baseStyle.backgroundSize = "200% 100%";
|
|
5345
4339
|
baseStyle.animation = `skeleton-wave ${motionSpeed}ms infinite linear`;
|
|
5346
|
-
} else if (
|
|
4340
|
+
} else if (pulse && isAnimating) {
|
|
5347
4341
|
baseStyle.animation = `skeleton-pulse ${motionSpeed}ms infinite ease-in-out`;
|
|
5348
4342
|
}
|
|
5349
4343
|
return baseStyle;
|
|
@@ -5668,16 +4662,2554 @@ function useElementProgress(options = {}) {
|
|
|
5668
4662
|
}, [start, end, clamp]);
|
|
5669
4663
|
return { ref, progress, isInView };
|
|
5670
4664
|
}
|
|
5671
|
-
function
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
4665
|
+
function useAutoFade(options = {}) {
|
|
4666
|
+
const {
|
|
4667
|
+
initialOpacity = 0,
|
|
4668
|
+
targetOpacity = 1,
|
|
4669
|
+
duration = 1e3,
|
|
4670
|
+
delay = 0,
|
|
4671
|
+
repeat = false,
|
|
4672
|
+
repeatDelay = 1e3,
|
|
4673
|
+
repeatCount = -1,
|
|
4674
|
+
ease = "ease-in-out",
|
|
4675
|
+
autoStart = true,
|
|
4676
|
+
onComplete,
|
|
4677
|
+
onRepeat,
|
|
4678
|
+
showOnMount = false
|
|
4679
|
+
} = options;
|
|
4680
|
+
const [opacity, setOpacity] = useState(showOnMount ? initialOpacity : 0);
|
|
4681
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
4682
|
+
const [isVisible, setIsVisible] = useState(
|
|
4683
|
+
showOnMount ? initialOpacity > 0 : false
|
|
4684
|
+
);
|
|
4685
|
+
const [mounted, setMounted] = useState(false);
|
|
4686
|
+
const motionRef = useRef(null);
|
|
4687
|
+
const timeoutRef = useRef(null);
|
|
4688
|
+
const repeatCountRef = useRef(0);
|
|
4689
|
+
const isFadingInRef = useRef(true);
|
|
4690
|
+
useEffect(() => {
|
|
4691
|
+
setMounted(true);
|
|
4692
|
+
}, []);
|
|
4693
|
+
const getEasing2 = useCallback(
|
|
4694
|
+
(t) => {
|
|
4695
|
+
switch (ease) {
|
|
4696
|
+
case "linear":
|
|
4697
|
+
return t;
|
|
4698
|
+
case "ease-in":
|
|
4699
|
+
return t * t;
|
|
4700
|
+
case "ease-out":
|
|
4701
|
+
return 1 - (1 - t) * (1 - t);
|
|
4702
|
+
case "ease-in-out":
|
|
4703
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
4704
|
+
default:
|
|
4705
|
+
return t;
|
|
4706
|
+
}
|
|
4707
|
+
},
|
|
4708
|
+
[ease]
|
|
4709
|
+
);
|
|
4710
|
+
const animate = useCallback(
|
|
4711
|
+
(from, to, onFinish) => {
|
|
4712
|
+
if (!mounted) return;
|
|
4713
|
+
setIsAnimating(true);
|
|
4714
|
+
const startTime = performance.now();
|
|
4715
|
+
const startOpacity = from;
|
|
4716
|
+
const updateOpacity = (currentTime) => {
|
|
4717
|
+
const elapsed = currentTime - startTime;
|
|
4718
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
4719
|
+
const easedProgress = getEasing2(progress);
|
|
4720
|
+
const currentOpacity = startOpacity + (to - startOpacity) * easedProgress;
|
|
4721
|
+
setOpacity(currentOpacity);
|
|
4722
|
+
setIsVisible(currentOpacity > 0);
|
|
4723
|
+
if (progress < 1) {
|
|
4724
|
+
motionRef.current = requestAnimationFrame(updateOpacity);
|
|
4725
|
+
} else {
|
|
4726
|
+
setIsAnimating(false);
|
|
4727
|
+
onFinish?.();
|
|
4728
|
+
}
|
|
4729
|
+
};
|
|
4730
|
+
motionRef.current = requestAnimationFrame(updateOpacity);
|
|
4731
|
+
},
|
|
4732
|
+
[mounted, duration, getEasing2]
|
|
4733
|
+
);
|
|
4734
|
+
const fadeIn = useCallback(() => {
|
|
4735
|
+
if (!mounted || isAnimating) return;
|
|
4736
|
+
animate(initialOpacity, targetOpacity, () => {
|
|
4737
|
+
onComplete?.();
|
|
4738
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
4739
|
+
repeatCountRef.current++;
|
|
4740
|
+
onRepeat?.(repeatCountRef.current);
|
|
4741
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
4742
|
+
fadeOut();
|
|
4743
|
+
}, repeatDelay);
|
|
4744
|
+
}
|
|
4745
|
+
});
|
|
4746
|
+
}, [
|
|
4747
|
+
mounted,
|
|
4748
|
+
isAnimating,
|
|
4749
|
+
animate,
|
|
4750
|
+
initialOpacity,
|
|
4751
|
+
targetOpacity,
|
|
4752
|
+
onComplete,
|
|
4753
|
+
repeat,
|
|
4754
|
+
repeatCount,
|
|
4755
|
+
repeatDelay,
|
|
4756
|
+
onRepeat
|
|
4757
|
+
]);
|
|
4758
|
+
const fadeOut = useCallback(() => {
|
|
4759
|
+
if (!mounted || isAnimating) return;
|
|
4760
|
+
animate(targetOpacity, initialOpacity, () => {
|
|
4761
|
+
onComplete?.();
|
|
4762
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
4763
|
+
repeatCountRef.current++;
|
|
4764
|
+
onRepeat?.(repeatCountRef.current);
|
|
4765
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
4766
|
+
fadeIn();
|
|
4767
|
+
}, repeatDelay);
|
|
4768
|
+
}
|
|
4769
|
+
});
|
|
4770
|
+
}, [
|
|
4771
|
+
mounted,
|
|
4772
|
+
isAnimating,
|
|
4773
|
+
animate,
|
|
4774
|
+
targetOpacity,
|
|
4775
|
+
initialOpacity,
|
|
4776
|
+
onComplete,
|
|
4777
|
+
repeat,
|
|
4778
|
+
repeatCount,
|
|
4779
|
+
repeatDelay,
|
|
4780
|
+
onRepeat
|
|
4781
|
+
]);
|
|
4782
|
+
const start = useCallback(() => {
|
|
4783
|
+
if (!mounted || isAnimating) return;
|
|
4784
|
+
if (delay > 0) {
|
|
4785
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
4786
|
+
fadeIn();
|
|
4787
|
+
}, delay);
|
|
4788
|
+
} else {
|
|
4789
|
+
fadeIn();
|
|
4790
|
+
}
|
|
4791
|
+
}, [mounted, isAnimating, delay, fadeIn]);
|
|
4792
|
+
const stop = useCallback(() => {
|
|
4793
|
+
if (motionRef.current !== null) {
|
|
4794
|
+
cancelAnimationFrame(motionRef.current);
|
|
4795
|
+
motionRef.current = null;
|
|
4796
|
+
}
|
|
4797
|
+
if (timeoutRef.current !== null) {
|
|
4798
|
+
clearTimeout(timeoutRef.current);
|
|
4799
|
+
timeoutRef.current = null;
|
|
4800
|
+
}
|
|
4801
|
+
setIsAnimating(false);
|
|
4802
|
+
}, []);
|
|
4803
|
+
const reset = useCallback(() => {
|
|
4804
|
+
stop();
|
|
4805
|
+
setOpacity(initialOpacity);
|
|
4806
|
+
setIsVisible(initialOpacity > 0);
|
|
4807
|
+
repeatCountRef.current = 0;
|
|
4808
|
+
isFadingInRef.current = true;
|
|
4809
|
+
}, [stop, initialOpacity]);
|
|
4810
|
+
const toggle = useCallback(() => {
|
|
4811
|
+
if (isFadingInRef.current) {
|
|
4812
|
+
fadeOut();
|
|
4813
|
+
isFadingInRef.current = false;
|
|
4814
|
+
} else {
|
|
4815
|
+
fadeIn();
|
|
4816
|
+
isFadingInRef.current = true;
|
|
4817
|
+
}
|
|
4818
|
+
}, [fadeIn, fadeOut]);
|
|
4819
|
+
useEffect(() => {
|
|
4820
|
+
if (mounted && autoStart) {
|
|
4821
|
+
start();
|
|
4822
|
+
}
|
|
4823
|
+
}, [mounted, autoStart, start]);
|
|
4824
|
+
useEffect(() => {
|
|
4825
|
+
return () => {
|
|
4826
|
+
if (motionRef.current !== null) {
|
|
4827
|
+
cancelAnimationFrame(motionRef.current);
|
|
4828
|
+
}
|
|
4829
|
+
if (timeoutRef.current !== null) {
|
|
4830
|
+
clearTimeout(timeoutRef.current);
|
|
4831
|
+
}
|
|
4832
|
+
};
|
|
4833
|
+
}, []);
|
|
4834
|
+
return {
|
|
4835
|
+
opacity,
|
|
4836
|
+
isAnimating,
|
|
4837
|
+
isVisible,
|
|
4838
|
+
mounted,
|
|
4839
|
+
start,
|
|
4840
|
+
stop,
|
|
4841
|
+
reset,
|
|
4842
|
+
fadeIn,
|
|
4843
|
+
fadeOut,
|
|
4844
|
+
toggle
|
|
4845
|
+
};
|
|
4846
|
+
}
|
|
4847
|
+
function useAutoPlay(options = {}) {
|
|
4848
|
+
const {
|
|
4849
|
+
interval = 3e3,
|
|
4850
|
+
delay = 0,
|
|
4851
|
+
repeat = "infinite",
|
|
4852
|
+
autoStart = true,
|
|
4853
|
+
pauseOnHover = false,
|
|
4854
|
+
pauseOnBlur = true,
|
|
4855
|
+
showOnMount = false
|
|
4856
|
+
} = options;
|
|
4857
|
+
const [isPlaying, setIsPlaying] = useState(showOnMount ? autoStart : false);
|
|
4858
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
4859
|
+
const [mounted, setMounted] = useState(false);
|
|
4860
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
4861
|
+
const intervalRef = useRef(null);
|
|
4862
|
+
const timeoutRef = useRef(null);
|
|
4863
|
+
const repeatCountRef = useRef(0);
|
|
4864
|
+
useEffect(() => {
|
|
4865
|
+
setMounted(true);
|
|
4866
|
+
}, []);
|
|
4867
|
+
const next = useCallback(() => {
|
|
4868
|
+
setCurrentStep((prev) => {
|
|
4869
|
+
const nextStep = prev + 1;
|
|
4870
|
+
if (repeat !== "infinite") {
|
|
4871
|
+
repeatCountRef.current += 1;
|
|
4872
|
+
if (repeatCountRef.current >= repeat) {
|
|
4873
|
+
stop();
|
|
4874
|
+
return prev;
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
return nextStep;
|
|
4878
|
+
});
|
|
4879
|
+
}, [repeat]);
|
|
4880
|
+
const startInterval = useCallback(() => {
|
|
4881
|
+
if (intervalRef.current) {
|
|
4882
|
+
clearInterval(intervalRef.current);
|
|
4883
|
+
}
|
|
4884
|
+
intervalRef.current = window.setInterval(() => {
|
|
4885
|
+
next();
|
|
4886
|
+
}, interval);
|
|
4887
|
+
}, [interval, next]);
|
|
4888
|
+
const pause = useCallback(() => {
|
|
4889
|
+
if (!isPlaying) return;
|
|
4890
|
+
setIsPaused(true);
|
|
4891
|
+
if (intervalRef.current) {
|
|
4892
|
+
clearInterval(intervalRef.current);
|
|
4893
|
+
intervalRef.current = null;
|
|
4894
|
+
}
|
|
4895
|
+
}, [isPlaying]);
|
|
4896
|
+
const resume = useCallback(() => {
|
|
4897
|
+
if (!isPlaying || !isPaused) return;
|
|
4898
|
+
setIsPaused(false);
|
|
4899
|
+
startInterval();
|
|
4900
|
+
}, [isPlaying, isPaused, startInterval]);
|
|
4901
|
+
const stop = useCallback(() => {
|
|
4902
|
+
setIsPlaying(false);
|
|
4903
|
+
setIsPaused(false);
|
|
4904
|
+
setCurrentStep(0);
|
|
4905
|
+
repeatCountRef.current = 0;
|
|
4906
|
+
if (intervalRef.current) {
|
|
4907
|
+
clearInterval(intervalRef.current);
|
|
4908
|
+
intervalRef.current = null;
|
|
4909
|
+
}
|
|
4910
|
+
if (timeoutRef.current) {
|
|
4911
|
+
clearTimeout(timeoutRef.current);
|
|
4912
|
+
timeoutRef.current = null;
|
|
4913
|
+
}
|
|
4914
|
+
}, []);
|
|
4915
|
+
const start = useCallback(() => {
|
|
4916
|
+
if (!mounted) return;
|
|
4917
|
+
setIsPlaying(true);
|
|
4918
|
+
setIsPaused(false);
|
|
4919
|
+
setCurrentStep(0);
|
|
4920
|
+
repeatCountRef.current = 0;
|
|
4921
|
+
if (delay > 0) {
|
|
4922
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
4923
|
+
startInterval();
|
|
4924
|
+
}, delay);
|
|
4925
|
+
} else {
|
|
4926
|
+
startInterval();
|
|
4927
|
+
}
|
|
4928
|
+
}, [mounted, delay, startInterval]);
|
|
4929
|
+
const previous = useCallback(() => {
|
|
4930
|
+
setCurrentStep((prev) => Math.max(0, prev - 1));
|
|
4931
|
+
}, []);
|
|
4932
|
+
const goTo = useCallback((step) => {
|
|
4933
|
+
setCurrentStep(Math.max(0, step));
|
|
4934
|
+
}, []);
|
|
4935
|
+
useEffect(() => {
|
|
4936
|
+
if (mounted && autoStart) {
|
|
4937
|
+
start();
|
|
4938
|
+
}
|
|
4939
|
+
}, [mounted, autoStart, start]);
|
|
4940
|
+
useEffect(() => {
|
|
4941
|
+
if (!pauseOnHover) return;
|
|
4942
|
+
const handleMouseEnter = () => {
|
|
4943
|
+
if (isPlaying && !isPaused) {
|
|
4944
|
+
pause();
|
|
4945
|
+
}
|
|
4946
|
+
};
|
|
4947
|
+
const handleMouseLeave = () => {
|
|
4948
|
+
if (isPlaying && isPaused) {
|
|
4949
|
+
resume();
|
|
4950
|
+
}
|
|
4951
|
+
};
|
|
4952
|
+
document.addEventListener("mouseenter", handleMouseEnter);
|
|
4953
|
+
document.addEventListener("mouseleave", handleMouseLeave);
|
|
4954
|
+
return () => {
|
|
4955
|
+
document.removeEventListener("mouseenter", handleMouseEnter);
|
|
4956
|
+
document.removeEventListener("mouseleave", handleMouseLeave);
|
|
4957
|
+
};
|
|
4958
|
+
}, [pauseOnHover, isPlaying, isPaused, pause, resume]);
|
|
4959
|
+
useEffect(() => {
|
|
4960
|
+
if (!pauseOnBlur) return;
|
|
4961
|
+
const handleBlur = () => {
|
|
4962
|
+
if (isPlaying && !isPaused) {
|
|
4963
|
+
pause();
|
|
4964
|
+
}
|
|
4965
|
+
};
|
|
4966
|
+
const handleFocus = () => {
|
|
4967
|
+
if (isPlaying && isPaused) {
|
|
4968
|
+
resume();
|
|
4969
|
+
}
|
|
4970
|
+
};
|
|
4971
|
+
window.addEventListener("blur", handleBlur);
|
|
4972
|
+
window.addEventListener("focus", handleFocus);
|
|
4973
|
+
return () => {
|
|
4974
|
+
window.removeEventListener("blur", handleBlur);
|
|
4975
|
+
window.removeEventListener("focus", handleFocus);
|
|
4976
|
+
};
|
|
4977
|
+
}, [pauseOnBlur, isPlaying, isPaused, pause, resume]);
|
|
4978
|
+
useEffect(() => {
|
|
4979
|
+
return () => {
|
|
4980
|
+
if (intervalRef.current) {
|
|
4981
|
+
clearInterval(intervalRef.current);
|
|
4982
|
+
}
|
|
4983
|
+
if (timeoutRef.current) {
|
|
4984
|
+
clearTimeout(timeoutRef.current);
|
|
4985
|
+
}
|
|
4986
|
+
};
|
|
4987
|
+
}, []);
|
|
4988
|
+
return {
|
|
4989
|
+
isPlaying,
|
|
4990
|
+
isPaused,
|
|
4991
|
+
currentStep,
|
|
4992
|
+
mounted,
|
|
4993
|
+
start,
|
|
4994
|
+
stop,
|
|
4995
|
+
pause,
|
|
4996
|
+
resume,
|
|
4997
|
+
next,
|
|
4998
|
+
previous,
|
|
4999
|
+
goTo
|
|
5000
|
+
};
|
|
5001
|
+
}
|
|
5002
|
+
function useAutoScale(options = {}) {
|
|
5003
|
+
const {
|
|
5004
|
+
initialScale = 0,
|
|
5005
|
+
targetScale = 1,
|
|
5006
|
+
duration = 1e3,
|
|
5007
|
+
delay = 0,
|
|
5008
|
+
repeat = false,
|
|
5009
|
+
repeatDelay = 1e3,
|
|
5010
|
+
repeatCount = -1,
|
|
5011
|
+
ease = "ease-in-out",
|
|
5012
|
+
autoStart = true,
|
|
5013
|
+
onComplete,
|
|
5014
|
+
onRepeat,
|
|
5015
|
+
showOnMount = false,
|
|
5016
|
+
centerTransform: _centerTransform = true
|
|
5017
|
+
} = options;
|
|
5018
|
+
const [scale, setScale] = useState(showOnMount ? initialScale : 0);
|
|
5019
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
5020
|
+
const [isVisible, setIsVisible] = useState(
|
|
5021
|
+
showOnMount ? initialScale > 0 : false
|
|
5022
|
+
);
|
|
5023
|
+
const [mounted, setMounted] = useState(false);
|
|
5024
|
+
const motionRef = useRef(null);
|
|
5025
|
+
const timeoutRef = useRef(null);
|
|
5026
|
+
const repeatCountRef = useRef(0);
|
|
5027
|
+
const isScalingInRef = useRef(true);
|
|
5028
|
+
useEffect(() => {
|
|
5029
|
+
setMounted(true);
|
|
5030
|
+
}, []);
|
|
5031
|
+
const getEasing2 = useCallback(
|
|
5032
|
+
(t) => {
|
|
5033
|
+
switch (ease) {
|
|
5034
|
+
case "linear":
|
|
5035
|
+
return t;
|
|
5036
|
+
case "ease-in":
|
|
5037
|
+
return t * t;
|
|
5038
|
+
case "ease-out":
|
|
5039
|
+
return 1 - (1 - t) * (1 - t);
|
|
5040
|
+
case "ease-in-out":
|
|
5041
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
5042
|
+
case "bounce":
|
|
5043
|
+
if (t < 1 / 2.75) {
|
|
5044
|
+
return 7.5625 * t * t;
|
|
5045
|
+
} else if (t < 2 / 2.75) {
|
|
5046
|
+
return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
|
|
5047
|
+
} else if (t < 2.5 / 2.75) {
|
|
5048
|
+
return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
|
|
5049
|
+
} else {
|
|
5050
|
+
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
|
|
5051
|
+
}
|
|
5052
|
+
case "elastic":
|
|
5053
|
+
if (t === 0) return 0;
|
|
5054
|
+
if (t === 1) return 1;
|
|
5055
|
+
return Math.pow(2, -10 * t) * Math.sin((t - 0.075) * (2 * Math.PI) / 0.3) + 1;
|
|
5056
|
+
default:
|
|
5057
|
+
return t;
|
|
5058
|
+
}
|
|
5059
|
+
},
|
|
5060
|
+
[ease]
|
|
5061
|
+
);
|
|
5062
|
+
const animate = useCallback(
|
|
5063
|
+
(from, to, onFinish) => {
|
|
5064
|
+
if (!mounted) return;
|
|
5065
|
+
setIsAnimating(true);
|
|
5066
|
+
const startTime = performance.now();
|
|
5067
|
+
const startScale = from;
|
|
5068
|
+
const updateScale = (currentTime) => {
|
|
5069
|
+
const elapsed = currentTime - startTime;
|
|
5070
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
5071
|
+
const easedProgress = getEasing2(progress);
|
|
5072
|
+
const currentScale = startScale + (to - startScale) * easedProgress;
|
|
5073
|
+
setScale(currentScale);
|
|
5074
|
+
setIsVisible(currentScale > 0);
|
|
5075
|
+
if (progress < 1) {
|
|
5076
|
+
motionRef.current = requestAnimationFrame(updateScale);
|
|
5077
|
+
} else {
|
|
5078
|
+
setIsAnimating(false);
|
|
5079
|
+
onFinish?.();
|
|
5080
|
+
}
|
|
5081
|
+
};
|
|
5082
|
+
motionRef.current = requestAnimationFrame(updateScale);
|
|
5083
|
+
},
|
|
5084
|
+
[mounted, duration, getEasing2]
|
|
5085
|
+
);
|
|
5086
|
+
const scaleIn = useCallback(() => {
|
|
5087
|
+
if (!mounted || isAnimating) return;
|
|
5088
|
+
animate(initialScale, targetScale, () => {
|
|
5089
|
+
onComplete?.();
|
|
5090
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
5091
|
+
repeatCountRef.current++;
|
|
5092
|
+
onRepeat?.(repeatCountRef.current);
|
|
5093
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5094
|
+
scaleOut();
|
|
5095
|
+
}, repeatDelay);
|
|
5096
|
+
}
|
|
5097
|
+
});
|
|
5098
|
+
}, [
|
|
5099
|
+
mounted,
|
|
5100
|
+
isAnimating,
|
|
5101
|
+
animate,
|
|
5102
|
+
initialScale,
|
|
5103
|
+
targetScale,
|
|
5104
|
+
onComplete,
|
|
5105
|
+
repeat,
|
|
5106
|
+
repeatCount,
|
|
5107
|
+
repeatDelay,
|
|
5108
|
+
onRepeat
|
|
5109
|
+
]);
|
|
5110
|
+
const scaleOut = useCallback(() => {
|
|
5111
|
+
if (!mounted || isAnimating) return;
|
|
5112
|
+
animate(targetScale, initialScale, () => {
|
|
5113
|
+
onComplete?.();
|
|
5114
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
5115
|
+
repeatCountRef.current++;
|
|
5116
|
+
onRepeat?.(repeatCountRef.current);
|
|
5117
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5118
|
+
scaleIn();
|
|
5119
|
+
}, repeatDelay);
|
|
5120
|
+
}
|
|
5121
|
+
});
|
|
5122
|
+
}, [
|
|
5123
|
+
mounted,
|
|
5124
|
+
isAnimating,
|
|
5125
|
+
animate,
|
|
5126
|
+
targetScale,
|
|
5127
|
+
initialScale,
|
|
5128
|
+
onComplete,
|
|
5129
|
+
repeat,
|
|
5130
|
+
repeatCount,
|
|
5131
|
+
repeatDelay,
|
|
5132
|
+
onRepeat
|
|
5133
|
+
]);
|
|
5134
|
+
const start = useCallback(() => {
|
|
5135
|
+
if (!mounted || isAnimating) return;
|
|
5136
|
+
if (delay > 0) {
|
|
5137
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5138
|
+
scaleIn();
|
|
5139
|
+
}, delay);
|
|
5140
|
+
} else {
|
|
5141
|
+
scaleIn();
|
|
5142
|
+
}
|
|
5143
|
+
}, [mounted, isAnimating, delay, scaleIn]);
|
|
5144
|
+
const stop = useCallback(() => {
|
|
5145
|
+
if (motionRef.current !== null) {
|
|
5146
|
+
cancelAnimationFrame(motionRef.current);
|
|
5147
|
+
motionRef.current = null;
|
|
5148
|
+
}
|
|
5149
|
+
if (timeoutRef.current !== null) {
|
|
5150
|
+
clearTimeout(timeoutRef.current);
|
|
5151
|
+
timeoutRef.current = null;
|
|
5152
|
+
}
|
|
5153
|
+
setIsAnimating(false);
|
|
5154
|
+
}, []);
|
|
5155
|
+
const reset = useCallback(() => {
|
|
5156
|
+
stop();
|
|
5157
|
+
setScale(initialScale);
|
|
5158
|
+
setIsVisible(initialScale > 0);
|
|
5159
|
+
repeatCountRef.current = 0;
|
|
5160
|
+
isScalingInRef.current = true;
|
|
5161
|
+
}, [stop, initialScale]);
|
|
5162
|
+
const toggle = useCallback(() => {
|
|
5163
|
+
if (isScalingInRef.current) {
|
|
5164
|
+
scaleOut();
|
|
5165
|
+
isScalingInRef.current = false;
|
|
5166
|
+
} else {
|
|
5167
|
+
scaleIn();
|
|
5168
|
+
isScalingInRef.current = true;
|
|
5169
|
+
}
|
|
5170
|
+
}, [scaleIn, scaleOut]);
|
|
5171
|
+
useEffect(() => {
|
|
5172
|
+
if (mounted && autoStart) {
|
|
5173
|
+
start();
|
|
5174
|
+
}
|
|
5175
|
+
}, [mounted, autoStart, start]);
|
|
5176
|
+
useEffect(() => {
|
|
5177
|
+
return () => {
|
|
5178
|
+
if (motionRef.current !== null) {
|
|
5179
|
+
cancelAnimationFrame(motionRef.current);
|
|
5180
|
+
}
|
|
5181
|
+
if (timeoutRef.current !== null) {
|
|
5182
|
+
clearTimeout(timeoutRef.current);
|
|
5183
|
+
}
|
|
5184
|
+
};
|
|
5185
|
+
}, []);
|
|
5186
|
+
return {
|
|
5187
|
+
scale,
|
|
5188
|
+
isAnimating,
|
|
5189
|
+
isVisible,
|
|
5190
|
+
mounted,
|
|
5191
|
+
start,
|
|
5192
|
+
stop,
|
|
5193
|
+
reset,
|
|
5194
|
+
scaleIn,
|
|
5195
|
+
scaleOut,
|
|
5196
|
+
toggle
|
|
5197
|
+
};
|
|
5198
|
+
}
|
|
5199
|
+
function useAutoSlide(options = {}) {
|
|
5200
|
+
const {
|
|
5201
|
+
direction = "left",
|
|
5202
|
+
distance = 100,
|
|
5203
|
+
initialPosition,
|
|
5204
|
+
targetPosition,
|
|
5205
|
+
duration = 1e3,
|
|
5206
|
+
delay = 0,
|
|
5207
|
+
repeat = false,
|
|
5208
|
+
repeatDelay = 1e3,
|
|
5209
|
+
repeatCount = -1,
|
|
5210
|
+
ease = "ease-in-out",
|
|
5211
|
+
autoStart = true,
|
|
5212
|
+
onComplete,
|
|
5213
|
+
onRepeat,
|
|
5214
|
+
showOnMount = false
|
|
5215
|
+
} = options;
|
|
5216
|
+
const getDefaultPositions = useCallback(() => {
|
|
5217
|
+
const defaultInitial = { x: 0, y: 0 };
|
|
5218
|
+
const defaultTarget = { x: 0, y: 0 };
|
|
5219
|
+
switch (direction) {
|
|
5220
|
+
case "left":
|
|
5221
|
+
defaultInitial.x = distance;
|
|
5222
|
+
defaultTarget.x = 0;
|
|
5223
|
+
break;
|
|
5224
|
+
case "right":
|
|
5225
|
+
defaultInitial.x = -distance;
|
|
5226
|
+
defaultTarget.x = 0;
|
|
5227
|
+
break;
|
|
5228
|
+
case "up":
|
|
5229
|
+
defaultInitial.y = distance;
|
|
5230
|
+
defaultTarget.y = 0;
|
|
5231
|
+
break;
|
|
5232
|
+
case "down":
|
|
5233
|
+
defaultInitial.y = -distance;
|
|
5234
|
+
defaultTarget.y = 0;
|
|
5235
|
+
break;
|
|
5236
|
+
case "left-up":
|
|
5237
|
+
defaultInitial.x = distance;
|
|
5238
|
+
defaultInitial.y = distance;
|
|
5239
|
+
defaultTarget.x = 0;
|
|
5240
|
+
defaultTarget.y = 0;
|
|
5241
|
+
break;
|
|
5242
|
+
case "left-down":
|
|
5243
|
+
defaultInitial.x = distance;
|
|
5244
|
+
defaultInitial.y = -distance;
|
|
5245
|
+
defaultTarget.x = 0;
|
|
5246
|
+
defaultTarget.y = 0;
|
|
5247
|
+
break;
|
|
5248
|
+
case "right-up":
|
|
5249
|
+
defaultInitial.x = -distance;
|
|
5250
|
+
defaultInitial.y = distance;
|
|
5251
|
+
defaultTarget.x = 0;
|
|
5252
|
+
defaultTarget.y = 0;
|
|
5253
|
+
break;
|
|
5254
|
+
case "right-down":
|
|
5255
|
+
defaultInitial.x = -distance;
|
|
5256
|
+
defaultInitial.y = -distance;
|
|
5257
|
+
defaultTarget.x = 0;
|
|
5258
|
+
defaultTarget.y = 0;
|
|
5259
|
+
break;
|
|
5260
|
+
}
|
|
5261
|
+
return {
|
|
5262
|
+
initial: initialPosition || defaultInitial,
|
|
5263
|
+
target: targetPosition || defaultTarget
|
|
5264
|
+
};
|
|
5265
|
+
}, [direction, distance, initialPosition, targetPosition]);
|
|
5266
|
+
const positions = getDefaultPositions();
|
|
5267
|
+
const [position, setPosition] = useState(
|
|
5268
|
+
showOnMount ? positions.initial : positions.target
|
|
5269
|
+
);
|
|
5270
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
5271
|
+
const [isVisible, setIsVisible] = useState(showOnMount ? true : false);
|
|
5272
|
+
const [mounted, setMounted] = useState(false);
|
|
5273
|
+
const motionRef = useRef(null);
|
|
5274
|
+
const timeoutRef = useRef(null);
|
|
5275
|
+
const repeatCountRef = useRef(0);
|
|
5276
|
+
const isSlidingInRef = useRef(true);
|
|
5277
|
+
useEffect(() => {
|
|
5278
|
+
setMounted(true);
|
|
5279
|
+
}, []);
|
|
5280
|
+
const getEasing2 = useCallback(
|
|
5281
|
+
(t) => {
|
|
5282
|
+
switch (ease) {
|
|
5283
|
+
case "linear":
|
|
5284
|
+
return t;
|
|
5285
|
+
case "ease-in":
|
|
5286
|
+
return t * t;
|
|
5287
|
+
case "ease-out":
|
|
5288
|
+
return 1 - (1 - t) * (1 - t);
|
|
5289
|
+
case "ease-in-out":
|
|
5290
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
5291
|
+
default:
|
|
5292
|
+
return t;
|
|
5293
|
+
}
|
|
5294
|
+
},
|
|
5295
|
+
[ease]
|
|
5296
|
+
);
|
|
5297
|
+
const animate = useCallback(
|
|
5298
|
+
(from, to, onFinish) => {
|
|
5299
|
+
if (!mounted) return;
|
|
5300
|
+
setIsAnimating(true);
|
|
5301
|
+
const startTime = performance.now();
|
|
5302
|
+
const startPosition = from;
|
|
5303
|
+
const updatePosition = (currentTime) => {
|
|
5304
|
+
const elapsed = currentTime - startTime;
|
|
5305
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
5306
|
+
const easedProgress = getEasing2(progress);
|
|
5307
|
+
const currentX = startPosition.x + (to.x - startPosition.x) * easedProgress;
|
|
5308
|
+
const currentY = startPosition.y + (to.y - startPosition.y) * easedProgress;
|
|
5309
|
+
setPosition({ x: currentX, y: currentY });
|
|
5310
|
+
setIsVisible(true);
|
|
5311
|
+
if (progress < 1) {
|
|
5312
|
+
motionRef.current = requestAnimationFrame(updatePosition);
|
|
5313
|
+
} else {
|
|
5314
|
+
setIsAnimating(false);
|
|
5315
|
+
onFinish?.();
|
|
5316
|
+
}
|
|
5317
|
+
};
|
|
5318
|
+
motionRef.current = requestAnimationFrame(updatePosition);
|
|
5319
|
+
},
|
|
5320
|
+
[mounted, duration, getEasing2]
|
|
5321
|
+
);
|
|
5322
|
+
const slideIn = useCallback(() => {
|
|
5323
|
+
if (!mounted || isAnimating) return;
|
|
5324
|
+
animate(positions.initial, positions.target, () => {
|
|
5325
|
+
onComplete?.();
|
|
5326
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
5327
|
+
repeatCountRef.current++;
|
|
5328
|
+
onRepeat?.(repeatCountRef.current);
|
|
5329
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5330
|
+
slideOut();
|
|
5331
|
+
}, repeatDelay);
|
|
5332
|
+
}
|
|
5333
|
+
});
|
|
5334
|
+
}, [
|
|
5335
|
+
mounted,
|
|
5336
|
+
isAnimating,
|
|
5337
|
+
animate,
|
|
5338
|
+
positions.initial,
|
|
5339
|
+
positions.target,
|
|
5340
|
+
onComplete,
|
|
5341
|
+
repeat,
|
|
5342
|
+
repeatCount,
|
|
5343
|
+
repeatDelay,
|
|
5344
|
+
onRepeat
|
|
5345
|
+
]);
|
|
5346
|
+
const slideOut = useCallback(() => {
|
|
5347
|
+
if (!mounted || isAnimating) return;
|
|
5348
|
+
animate(positions.target, positions.initial, () => {
|
|
5349
|
+
onComplete?.();
|
|
5350
|
+
if (repeat && (repeatCount === -1 || repeatCountRef.current < repeatCount)) {
|
|
5351
|
+
repeatCountRef.current++;
|
|
5352
|
+
onRepeat?.(repeatCountRef.current);
|
|
5353
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5354
|
+
slideIn();
|
|
5355
|
+
}, repeatDelay);
|
|
5356
|
+
}
|
|
5357
|
+
});
|
|
5358
|
+
}, [
|
|
5359
|
+
mounted,
|
|
5360
|
+
isAnimating,
|
|
5361
|
+
animate,
|
|
5362
|
+
positions.target,
|
|
5363
|
+
positions.initial,
|
|
5364
|
+
onComplete,
|
|
5365
|
+
repeat,
|
|
5366
|
+
repeatCount,
|
|
5367
|
+
repeatDelay,
|
|
5368
|
+
onRepeat
|
|
5369
|
+
]);
|
|
5370
|
+
const start = useCallback(() => {
|
|
5371
|
+
if (!mounted || isAnimating) return;
|
|
5372
|
+
if (delay > 0) {
|
|
5373
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
5374
|
+
slideIn();
|
|
5375
|
+
}, delay);
|
|
5376
|
+
} else {
|
|
5377
|
+
slideIn();
|
|
5378
|
+
}
|
|
5379
|
+
}, [mounted, isAnimating, delay, slideIn]);
|
|
5380
|
+
const stop = useCallback(() => {
|
|
5381
|
+
if (motionRef.current !== null) {
|
|
5382
|
+
cancelAnimationFrame(motionRef.current);
|
|
5383
|
+
motionRef.current = null;
|
|
5384
|
+
}
|
|
5385
|
+
if (timeoutRef.current !== null) {
|
|
5386
|
+
clearTimeout(timeoutRef.current);
|
|
5387
|
+
timeoutRef.current = null;
|
|
5388
|
+
}
|
|
5389
|
+
setIsAnimating(false);
|
|
5390
|
+
}, []);
|
|
5391
|
+
const reset = useCallback(() => {
|
|
5392
|
+
stop();
|
|
5393
|
+
setPosition(positions.initial);
|
|
5394
|
+
setIsVisible(showOnMount ? true : false);
|
|
5395
|
+
repeatCountRef.current = 0;
|
|
5396
|
+
isSlidingInRef.current = true;
|
|
5397
|
+
}, [stop, positions.initial, showOnMount]);
|
|
5398
|
+
const toggle = useCallback(() => {
|
|
5399
|
+
if (isSlidingInRef.current) {
|
|
5400
|
+
slideOut();
|
|
5401
|
+
isSlidingInRef.current = false;
|
|
5402
|
+
} else {
|
|
5403
|
+
slideIn();
|
|
5404
|
+
isSlidingInRef.current = true;
|
|
5405
|
+
}
|
|
5406
|
+
}, [slideIn, slideOut]);
|
|
5407
|
+
useEffect(() => {
|
|
5408
|
+
if (mounted && autoStart) {
|
|
5409
|
+
start();
|
|
5410
|
+
}
|
|
5411
|
+
}, [mounted, autoStart, start]);
|
|
5412
|
+
useEffect(() => {
|
|
5413
|
+
return () => {
|
|
5414
|
+
if (motionRef.current !== null) {
|
|
5415
|
+
cancelAnimationFrame(motionRef.current);
|
|
5416
|
+
}
|
|
5417
|
+
if (timeoutRef.current !== null) {
|
|
5418
|
+
clearTimeout(timeoutRef.current);
|
|
5419
|
+
}
|
|
5420
|
+
};
|
|
5421
|
+
}, []);
|
|
5422
|
+
return {
|
|
5423
|
+
position,
|
|
5424
|
+
isAnimating,
|
|
5425
|
+
isVisible,
|
|
5426
|
+
mounted,
|
|
5427
|
+
start,
|
|
5428
|
+
stop,
|
|
5429
|
+
reset,
|
|
5430
|
+
slideIn,
|
|
5431
|
+
slideOut,
|
|
5432
|
+
toggle
|
|
5433
|
+
};
|
|
5434
|
+
}
|
|
5435
|
+
function useMotionOrchestra(options = {}) {
|
|
5436
|
+
const {
|
|
5437
|
+
mode = "sequential",
|
|
5438
|
+
staggerDelay = 100,
|
|
5439
|
+
autoStart = false,
|
|
5440
|
+
loop = false,
|
|
5441
|
+
onComplete
|
|
5442
|
+
} = options;
|
|
5443
|
+
const [orchestraState, setOrchestraState] = useState({
|
|
5444
|
+
isPlaying: false,
|
|
5445
|
+
currentStep: 0,
|
|
5446
|
+
completedSteps: /* @__PURE__ */ new Set()
|
|
5447
|
+
});
|
|
5448
|
+
const motionsRef = useRef([]);
|
|
5449
|
+
const timeoutsRef = useRef([]);
|
|
5450
|
+
const addMotion = useCallback((step) => {
|
|
5451
|
+
motionsRef.current.push(step);
|
|
5452
|
+
}, []);
|
|
5453
|
+
const removeMotion = useCallback((id) => {
|
|
5454
|
+
motionsRef.current = motionsRef.current.filter((step) => step.id !== id);
|
|
5455
|
+
}, []);
|
|
5456
|
+
const clearTimeouts = useCallback(() => {
|
|
5457
|
+
timeoutsRef.current.forEach((timeout) => clearTimeout(timeout));
|
|
5458
|
+
timeoutsRef.current = [];
|
|
5459
|
+
}, []);
|
|
5460
|
+
const playSequential = useCallback(() => {
|
|
5461
|
+
if (motionsRef.current.length === 0) return;
|
|
5462
|
+
const playStep = (index) => {
|
|
5463
|
+
if (index >= motionsRef.current.length) {
|
|
5464
|
+
setOrchestraState((prev) => ({
|
|
5465
|
+
...prev,
|
|
5466
|
+
isPlaying: false,
|
|
5467
|
+
currentStep: 0
|
|
5468
|
+
}));
|
|
5469
|
+
onComplete?.();
|
|
5470
|
+
if (loop) {
|
|
5471
|
+
setTimeout(() => {
|
|
5472
|
+
setOrchestraState((prev) => ({
|
|
5473
|
+
...prev,
|
|
5474
|
+
isPlaying: true,
|
|
5475
|
+
completedSteps: /* @__PURE__ */ new Set()
|
|
5476
|
+
}));
|
|
5477
|
+
playSequential();
|
|
5478
|
+
}, 1e3);
|
|
5479
|
+
}
|
|
5480
|
+
return;
|
|
5481
|
+
}
|
|
5482
|
+
const step = motionsRef.current[index];
|
|
5483
|
+
setOrchestraState((prev) => ({
|
|
5484
|
+
...prev,
|
|
5485
|
+
currentStep: index,
|
|
5486
|
+
completedSteps: /* @__PURE__ */ new Set([...prev.completedSteps, step.id])
|
|
5487
|
+
}));
|
|
5488
|
+
step.motion();
|
|
5489
|
+
if (step.onComplete) {
|
|
5490
|
+
step.onComplete();
|
|
5491
|
+
}
|
|
5492
|
+
const timeout = setTimeout(() => {
|
|
5493
|
+
playStep(index + 1);
|
|
5494
|
+
}, step.delay || 0);
|
|
5495
|
+
timeoutsRef.current.push(timeout);
|
|
5496
|
+
};
|
|
5497
|
+
playStep(0);
|
|
5498
|
+
}, [loop, onComplete]);
|
|
5499
|
+
const playParallel = useCallback(() => {
|
|
5500
|
+
if (motionsRef.current.length === 0) return;
|
|
5501
|
+
const completedSteps = /* @__PURE__ */ new Set();
|
|
5502
|
+
motionsRef.current.forEach((step) => {
|
|
5503
|
+
const timeout = setTimeout(() => {
|
|
5504
|
+
step.motion();
|
|
5505
|
+
completedSteps.add(step.id);
|
|
5506
|
+
if (step.onComplete) {
|
|
5507
|
+
step.onComplete();
|
|
5508
|
+
}
|
|
5509
|
+
if (completedSteps.size === motionsRef.current.length) {
|
|
5510
|
+
setOrchestraState((prev) => ({
|
|
5511
|
+
...prev,
|
|
5512
|
+
isPlaying: false,
|
|
5513
|
+
currentStep: 0
|
|
5514
|
+
}));
|
|
5515
|
+
onComplete?.();
|
|
5516
|
+
if (loop) {
|
|
5517
|
+
setTimeout(() => {
|
|
5518
|
+
setOrchestraState((prev) => ({ ...prev, isPlaying: true }));
|
|
5519
|
+
playParallel();
|
|
5520
|
+
}, 1e3);
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
}, step.delay || 0);
|
|
5524
|
+
timeoutsRef.current.push(timeout);
|
|
5525
|
+
});
|
|
5526
|
+
setOrchestraState((prev) => ({
|
|
5527
|
+
...prev,
|
|
5528
|
+
completedSteps: new Set(completedSteps)
|
|
5529
|
+
}));
|
|
5530
|
+
}, [loop, onComplete]);
|
|
5531
|
+
const playStagger = useCallback(() => {
|
|
5532
|
+
if (motionsRef.current.length === 0) return;
|
|
5533
|
+
const completedSteps = /* @__PURE__ */ new Set();
|
|
5534
|
+
motionsRef.current.forEach((step, index) => {
|
|
5535
|
+
const timeout = setTimeout(
|
|
5536
|
+
() => {
|
|
5537
|
+
step.motion();
|
|
5538
|
+
completedSteps.add(step.id);
|
|
5539
|
+
if (step.onComplete) {
|
|
5540
|
+
step.onComplete();
|
|
5541
|
+
}
|
|
5542
|
+
setOrchestraState((prev) => ({
|
|
5543
|
+
...prev,
|
|
5544
|
+
currentStep: index,
|
|
5545
|
+
completedSteps: /* @__PURE__ */ new Set([...prev.completedSteps, step.id])
|
|
5546
|
+
}));
|
|
5547
|
+
if (completedSteps.size === motionsRef.current.length) {
|
|
5548
|
+
setOrchestraState((prev) => ({
|
|
5549
|
+
...prev,
|
|
5550
|
+
isPlaying: false,
|
|
5551
|
+
currentStep: 0
|
|
5552
|
+
}));
|
|
5553
|
+
onComplete?.();
|
|
5554
|
+
if (loop) {
|
|
5555
|
+
setTimeout(() => {
|
|
5556
|
+
setOrchestraState((prev) => ({
|
|
5557
|
+
...prev,
|
|
5558
|
+
isPlaying: true,
|
|
5559
|
+
completedSteps: /* @__PURE__ */ new Set()
|
|
5560
|
+
}));
|
|
5561
|
+
playStagger();
|
|
5562
|
+
}, 1e3);
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
},
|
|
5566
|
+
(step.delay || 0) + index * staggerDelay
|
|
5567
|
+
);
|
|
5568
|
+
timeoutsRef.current.push(timeout);
|
|
5569
|
+
});
|
|
5570
|
+
}, [staggerDelay, loop, onComplete]);
|
|
5571
|
+
const play = useCallback(() => {
|
|
5572
|
+
clearTimeouts();
|
|
5573
|
+
setOrchestraState((prev) => ({
|
|
5574
|
+
...prev,
|
|
5575
|
+
isPlaying: true,
|
|
5576
|
+
currentStep: 0,
|
|
5577
|
+
completedSteps: /* @__PURE__ */ new Set()
|
|
5578
|
+
}));
|
|
5579
|
+
switch (mode) {
|
|
5580
|
+
case "sequential":
|
|
5581
|
+
playSequential();
|
|
5582
|
+
break;
|
|
5583
|
+
case "parallel":
|
|
5584
|
+
playParallel();
|
|
5585
|
+
break;
|
|
5586
|
+
case "stagger":
|
|
5587
|
+
playStagger();
|
|
5588
|
+
break;
|
|
5589
|
+
}
|
|
5590
|
+
}, [mode, clearTimeouts, playSequential, playParallel, playStagger]);
|
|
5591
|
+
const stop = useCallback(() => {
|
|
5592
|
+
clearTimeouts();
|
|
5593
|
+
setOrchestraState((prev) => ({
|
|
5594
|
+
...prev,
|
|
5595
|
+
isPlaying: false,
|
|
5596
|
+
currentStep: 0
|
|
5597
|
+
}));
|
|
5598
|
+
}, [clearTimeouts]);
|
|
5599
|
+
const pause = useCallback(() => {
|
|
5600
|
+
setOrchestraState((prev) => ({ ...prev, isPlaying: false }));
|
|
5601
|
+
}, []);
|
|
5602
|
+
const resume = useCallback(() => {
|
|
5603
|
+
if (orchestraState.currentStep < motionsRef.current.length) {
|
|
5604
|
+
setOrchestraState((prev) => ({ ...prev, isPlaying: true }));
|
|
5605
|
+
switch (mode) {
|
|
5606
|
+
case "sequential":
|
|
5607
|
+
playSequential();
|
|
5608
|
+
break;
|
|
5609
|
+
case "parallel":
|
|
5610
|
+
playParallel();
|
|
5611
|
+
break;
|
|
5612
|
+
case "stagger":
|
|
5613
|
+
playStagger();
|
|
5614
|
+
break;
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
}, [
|
|
5618
|
+
mode,
|
|
5619
|
+
orchestraState.currentStep,
|
|
5620
|
+
playSequential,
|
|
5621
|
+
playParallel,
|
|
5622
|
+
playStagger
|
|
5623
|
+
]);
|
|
5624
|
+
useEffect(() => {
|
|
5625
|
+
if (autoStart && motionsRef.current.length > 0) {
|
|
5626
|
+
play();
|
|
5627
|
+
}
|
|
5628
|
+
}, [autoStart, play]);
|
|
5629
|
+
useEffect(() => {
|
|
5630
|
+
return () => {
|
|
5631
|
+
clearTimeouts();
|
|
5632
|
+
};
|
|
5633
|
+
}, [clearTimeouts]);
|
|
5634
|
+
return {
|
|
5635
|
+
addMotion,
|
|
5636
|
+
removeMotion,
|
|
5637
|
+
play,
|
|
5638
|
+
stop,
|
|
5639
|
+
pause,
|
|
5640
|
+
resume,
|
|
5641
|
+
isPlaying: orchestraState.isPlaying,
|
|
5642
|
+
currentStep: orchestraState.currentStep,
|
|
5643
|
+
completedSteps: orchestraState.completedSteps,
|
|
5644
|
+
totalSteps: motionsRef.current.length
|
|
5645
|
+
};
|
|
5646
|
+
}
|
|
5647
|
+
function useOrchestration(options = {}) {
|
|
5648
|
+
const {
|
|
5649
|
+
autoStart = false,
|
|
5650
|
+
loop = false,
|
|
5651
|
+
loopCount = -1,
|
|
5652
|
+
loopDelay = 1e3,
|
|
5653
|
+
timeline = [],
|
|
5654
|
+
duration: totalDuration,
|
|
5655
|
+
speed = 1,
|
|
5656
|
+
reverse = false,
|
|
5657
|
+
onStart,
|
|
5658
|
+
onComplete,
|
|
5659
|
+
onLoop,
|
|
5660
|
+
onError: _onError,
|
|
5661
|
+
onProgress,
|
|
5662
|
+
onStepStart,
|
|
5663
|
+
onStepComplete
|
|
5664
|
+
} = options;
|
|
5665
|
+
const [state, setState] = useState({
|
|
5666
|
+
isPlaying: false,
|
|
5667
|
+
isPaused: false,
|
|
5668
|
+
currentTime: 0,
|
|
5669
|
+
progress: 0,
|
|
5670
|
+
currentStep: null,
|
|
5671
|
+
loopCount: 0,
|
|
5672
|
+
error: null
|
|
5673
|
+
});
|
|
5674
|
+
const [steps, setSteps] = useState(timeline);
|
|
5675
|
+
const [currentSpeed, setCurrentSpeed] = useState(speed);
|
|
5676
|
+
const [isReversed, setIsReversed] = useState(reverse);
|
|
5677
|
+
const motionRef = useRef(null);
|
|
5678
|
+
const startTimeRef = useRef(0);
|
|
5679
|
+
const pauseTimeRef = useRef(0);
|
|
5680
|
+
const stepStartTimesRef = useRef(/* @__PURE__ */ new Map());
|
|
5681
|
+
const stepDurationsRef = useRef(/* @__PURE__ */ new Map());
|
|
5682
|
+
const getEasing2 = useCallback(
|
|
5683
|
+
(t, ease = "linear") => {
|
|
5684
|
+
switch (ease) {
|
|
5685
|
+
case "linear":
|
|
5686
|
+
return t;
|
|
5687
|
+
case "ease-in":
|
|
5688
|
+
return t * t;
|
|
5689
|
+
case "ease-out":
|
|
5690
|
+
return 1 - (1 - t) * (1 - t);
|
|
5691
|
+
case "ease-in-out":
|
|
5692
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
5693
|
+
case "bounce":
|
|
5694
|
+
if (t < 1 / 2.75) {
|
|
5695
|
+
return 7.5625 * t * t;
|
|
5696
|
+
} else if (t < 2 / 2.75) {
|
|
5697
|
+
return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
|
|
5698
|
+
} else if (t < 2.5 / 2.75) {
|
|
5699
|
+
return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
|
|
5700
|
+
} else {
|
|
5701
|
+
return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
|
|
5702
|
+
}
|
|
5703
|
+
case "elastic":
|
|
5704
|
+
if (t === 0) return 0;
|
|
5705
|
+
if (t === 1) return 1;
|
|
5706
|
+
return Math.pow(2, -10 * t) * Math.sin((t - 0.075) * (2 * Math.PI) / 0.3) + 1;
|
|
5707
|
+
default:
|
|
5708
|
+
return t;
|
|
5709
|
+
}
|
|
5710
|
+
},
|
|
5711
|
+
[]
|
|
5712
|
+
);
|
|
5713
|
+
const getTotalDuration = useCallback(() => {
|
|
5714
|
+
if (totalDuration) return totalDuration;
|
|
5715
|
+
let maxEndTime = 0;
|
|
5716
|
+
steps.forEach((step) => {
|
|
5717
|
+
const stepEndTime = (step.delay || 0) + step.duration;
|
|
5718
|
+
maxEndTime = Math.max(maxEndTime, stepEndTime);
|
|
5719
|
+
});
|
|
5720
|
+
return maxEndTime;
|
|
5721
|
+
}, [steps, totalDuration]);
|
|
5722
|
+
const calculateStepTimes = useCallback(() => {
|
|
5723
|
+
const stepTimes = /* @__PURE__ */ new Map();
|
|
5724
|
+
const stepDurations = /* @__PURE__ */ new Map();
|
|
5725
|
+
let currentTime = 0;
|
|
5726
|
+
steps.forEach((step) => {
|
|
5727
|
+
stepTimes.set(step.id, currentTime + (step.delay || 0));
|
|
5728
|
+
stepDurations.set(step.id, step.duration);
|
|
5729
|
+
currentTime += (step.delay || 0) + step.duration;
|
|
5730
|
+
});
|
|
5731
|
+
stepStartTimesRef.current = stepTimes;
|
|
5732
|
+
stepDurationsRef.current = stepDurations;
|
|
5733
|
+
}, [steps]);
|
|
5734
|
+
const getCurrentStep = useCallback(
|
|
5735
|
+
(time) => {
|
|
5736
|
+
for (const step of steps) {
|
|
5737
|
+
const startTime = stepStartTimesRef.current.get(step.id) || 0;
|
|
5738
|
+
const endTime = startTime + (stepDurationsRef.current.get(step.id) || 0);
|
|
5739
|
+
if (time >= startTime && time <= endTime) {
|
|
5740
|
+
return step.id;
|
|
5741
|
+
}
|
|
5742
|
+
}
|
|
5743
|
+
return null;
|
|
5744
|
+
},
|
|
5745
|
+
[steps]
|
|
5746
|
+
);
|
|
5747
|
+
const getStepProgress = useCallback(
|
|
5748
|
+
(stepId) => {
|
|
5749
|
+
const startTime = stepStartTimesRef.current.get(stepId) || 0;
|
|
5750
|
+
const duration = stepDurationsRef.current.get(stepId) || 0;
|
|
5751
|
+
const currentTime = state.currentTime;
|
|
5752
|
+
if (currentTime < startTime) return 0;
|
|
5753
|
+
if (currentTime > startTime + duration) return 1;
|
|
5754
|
+
const stepProgress = (currentTime - startTime) / duration;
|
|
5755
|
+
return Math.max(0, Math.min(1, stepProgress));
|
|
5756
|
+
},
|
|
5757
|
+
[state.currentTime]
|
|
5758
|
+
);
|
|
5759
|
+
const getStepTime = useCallback(
|
|
5760
|
+
(stepId) => {
|
|
5761
|
+
const startTime = stepStartTimesRef.current.get(stepId) || 0;
|
|
5762
|
+
const duration = stepDurationsRef.current.get(stepId) || 0;
|
|
5763
|
+
const currentTime = state.currentTime;
|
|
5764
|
+
if (currentTime < startTime) return 0;
|
|
5765
|
+
if (currentTime > startTime + duration) return duration;
|
|
5766
|
+
return currentTime - startTime;
|
|
5767
|
+
},
|
|
5768
|
+
[state.currentTime]
|
|
5769
|
+
);
|
|
5770
|
+
const updateMotion = useCallback(
|
|
5771
|
+
(currentTime) => {
|
|
5772
|
+
const total = getTotalDuration();
|
|
5773
|
+
const adjustedTime = isReversed ? total - currentTime : currentTime;
|
|
5774
|
+
const progress = Math.min(adjustedTime / total, 1);
|
|
5775
|
+
const currentStep = getCurrentStep(adjustedTime);
|
|
5776
|
+
setState((prev) => ({
|
|
5777
|
+
...prev,
|
|
5778
|
+
currentTime: adjustedTime,
|
|
5779
|
+
progress,
|
|
5780
|
+
currentStep
|
|
5781
|
+
}));
|
|
5782
|
+
onProgress?.(progress);
|
|
5783
|
+
if (currentStep) {
|
|
5784
|
+
const step = steps.find((s) => s.id === currentStep);
|
|
5785
|
+
if (step) {
|
|
5786
|
+
const stepProgress = getStepProgress(currentStep);
|
|
5787
|
+
const easedProgress = getEasing2(stepProgress, step.ease);
|
|
5788
|
+
step.onUpdate?.(easedProgress);
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
if (progress >= 1) {
|
|
5792
|
+
if (loop && (loopCount === -1 || state.loopCount < loopCount)) {
|
|
5793
|
+
setState((prev) => ({
|
|
5794
|
+
...prev,
|
|
5795
|
+
loopCount: prev.loopCount + 1
|
|
5796
|
+
}));
|
|
5797
|
+
onLoop?.(state.loopCount + 1);
|
|
5798
|
+
setTimeout(() => {
|
|
5799
|
+
reset();
|
|
5800
|
+
play();
|
|
5801
|
+
}, loopDelay);
|
|
5802
|
+
} else {
|
|
5803
|
+
setState((prev) => ({
|
|
5804
|
+
...prev,
|
|
5805
|
+
isPlaying: false,
|
|
5806
|
+
currentTime: isReversed ? 0 : total,
|
|
5807
|
+
progress: 1
|
|
5808
|
+
}));
|
|
5809
|
+
onComplete?.();
|
|
5810
|
+
}
|
|
5811
|
+
} else {
|
|
5812
|
+
motionRef.current = requestAnimationFrame(() => {
|
|
5813
|
+
const elapsed = (performance.now() - startTimeRef.current) * currentSpeed / 1e3;
|
|
5814
|
+
updateMotion(elapsed);
|
|
5815
|
+
});
|
|
5816
|
+
}
|
|
5817
|
+
},
|
|
5818
|
+
[
|
|
5819
|
+
getTotalDuration,
|
|
5820
|
+
isReversed,
|
|
5821
|
+
getCurrentStep,
|
|
5822
|
+
onProgress,
|
|
5823
|
+
steps,
|
|
5824
|
+
getStepProgress,
|
|
5825
|
+
getEasing2,
|
|
5826
|
+
loop,
|
|
5827
|
+
loopCount,
|
|
5828
|
+
state.loopCount,
|
|
5829
|
+
loopDelay,
|
|
5830
|
+
onLoop,
|
|
5831
|
+
onComplete,
|
|
5832
|
+
currentSpeed
|
|
5833
|
+
]
|
|
5834
|
+
);
|
|
5835
|
+
const play = useCallback(() => {
|
|
5836
|
+
if (state.isPlaying) return;
|
|
5837
|
+
setState((prev) => ({
|
|
5838
|
+
...prev,
|
|
5839
|
+
isPlaying: true,
|
|
5840
|
+
isPaused: false,
|
|
5841
|
+
error: null
|
|
5842
|
+
}));
|
|
5843
|
+
onStart?.();
|
|
5844
|
+
const startTime = performance.now() - state.currentTime * 1e3 / currentSpeed;
|
|
5845
|
+
startTimeRef.current = startTime;
|
|
5846
|
+
motionRef.current = requestAnimationFrame(() => {
|
|
5847
|
+
const elapsed = (performance.now() - startTimeRef.current) * currentSpeed / 1e3;
|
|
5848
|
+
updateMotion(elapsed);
|
|
5849
|
+
});
|
|
5850
|
+
}, [state.isPlaying, state.currentTime, currentSpeed, onStart, updateMotion]);
|
|
5851
|
+
const pause = useCallback(() => {
|
|
5852
|
+
if (!state.isPlaying || state.isPaused) return;
|
|
5853
|
+
setState((prev) => ({
|
|
5854
|
+
...prev,
|
|
5855
|
+
isPaused: true
|
|
5856
|
+
}));
|
|
5857
|
+
if (motionRef.current) {
|
|
5858
|
+
cancelAnimationFrame(motionRef.current);
|
|
5859
|
+
motionRef.current = null;
|
|
5860
|
+
}
|
|
5861
|
+
pauseTimeRef.current = state.currentTime;
|
|
5862
|
+
}, [state.isPlaying, state.isPaused, state.currentTime]);
|
|
5863
|
+
const stop = useCallback(() => {
|
|
5864
|
+
setState((prev) => ({
|
|
5865
|
+
...prev,
|
|
5866
|
+
isPlaying: false,
|
|
5867
|
+
isPaused: false,
|
|
5868
|
+
currentTime: 0,
|
|
5869
|
+
progress: 0,
|
|
5870
|
+
currentStep: null
|
|
5871
|
+
}));
|
|
5872
|
+
if (motionRef.current) {
|
|
5873
|
+
cancelAnimationFrame(motionRef.current);
|
|
5874
|
+
motionRef.current = null;
|
|
5875
|
+
}
|
|
5876
|
+
}, []);
|
|
5877
|
+
const reset = useCallback(() => {
|
|
5878
|
+
stop();
|
|
5879
|
+
setState((prev) => ({
|
|
5880
|
+
...prev,
|
|
5881
|
+
currentTime: 0,
|
|
5882
|
+
progress: 0,
|
|
5883
|
+
currentStep: null,
|
|
5884
|
+
loopCount: 0
|
|
5885
|
+
}));
|
|
5886
|
+
}, [stop]);
|
|
5887
|
+
const seek = useCallback(
|
|
5888
|
+
(time) => {
|
|
5889
|
+
const total = getTotalDuration();
|
|
5890
|
+
const clampedTime = Math.max(0, Math.min(time, total));
|
|
5891
|
+
setState((prev) => ({
|
|
5892
|
+
...prev,
|
|
5893
|
+
currentTime: clampedTime,
|
|
5894
|
+
progress: clampedTime / total,
|
|
5895
|
+
currentStep: getCurrentStep(clampedTime)
|
|
5896
|
+
}));
|
|
5897
|
+
},
|
|
5898
|
+
[getTotalDuration, getCurrentStep]
|
|
5899
|
+
);
|
|
5900
|
+
const setSpeed = useCallback((speed2) => {
|
|
5901
|
+
setCurrentSpeed(Math.max(0.1, speed2));
|
|
5902
|
+
}, []);
|
|
5903
|
+
const reverseDirection = useCallback(() => {
|
|
5904
|
+
setIsReversed((prev) => !prev);
|
|
5905
|
+
}, []);
|
|
5906
|
+
const addStep = useCallback((step) => {
|
|
5907
|
+
setSteps((prev) => [...prev, step]);
|
|
5908
|
+
}, []);
|
|
5909
|
+
const removeStep = useCallback((stepId) => {
|
|
5910
|
+
setSteps((prev) => prev.filter((step) => step.id !== stepId));
|
|
5911
|
+
}, []);
|
|
5912
|
+
const updateStep = useCallback(
|
|
5913
|
+
(stepId, updates) => {
|
|
5914
|
+
setSteps(
|
|
5915
|
+
(prev) => prev.map(
|
|
5916
|
+
(step) => step.id === stepId ? { ...step, ...updates } : step
|
|
5917
|
+
)
|
|
5918
|
+
);
|
|
5919
|
+
},
|
|
5920
|
+
[]
|
|
5921
|
+
);
|
|
5922
|
+
const reorderSteps = useCallback((stepIds) => {
|
|
5923
|
+
setSteps((prev) => {
|
|
5924
|
+
const stepMap = new Map(prev.map((step) => [step.id, step]));
|
|
5925
|
+
return stepIds.map((id) => stepMap.get(id)).filter(Boolean);
|
|
5926
|
+
});
|
|
5927
|
+
}, []);
|
|
5928
|
+
useEffect(() => {
|
|
5929
|
+
const currentStep = state.currentStep;
|
|
5930
|
+
if (currentStep) {
|
|
5931
|
+
onStepStart?.(currentStep);
|
|
5932
|
+
const step = steps.find((s) => s.id === currentStep);
|
|
5933
|
+
step?.onStart?.();
|
|
5934
|
+
}
|
|
5935
|
+
}, [state.currentStep, steps, onStepStart]);
|
|
5936
|
+
useEffect(() => {
|
|
5937
|
+
const currentStep = state.currentStep;
|
|
5938
|
+
if (currentStep) {
|
|
5939
|
+
const stepProgress = getStepProgress(currentStep);
|
|
5940
|
+
if (stepProgress >= 1) {
|
|
5941
|
+
const step = steps.find((s) => s.id === currentStep);
|
|
5942
|
+
step?.onComplete?.();
|
|
5943
|
+
onStepComplete?.(currentStep);
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
}, [
|
|
5947
|
+
state.currentTime,
|
|
5948
|
+
state.currentStep,
|
|
5949
|
+
steps,
|
|
5950
|
+
getStepProgress,
|
|
5951
|
+
onStepComplete
|
|
5952
|
+
]);
|
|
5953
|
+
useEffect(() => {
|
|
5954
|
+
calculateStepTimes();
|
|
5955
|
+
}, [calculateStepTimes]);
|
|
5956
|
+
useEffect(() => {
|
|
5957
|
+
if (autoStart && steps.length > 0) {
|
|
5958
|
+
play();
|
|
5959
|
+
}
|
|
5960
|
+
}, [autoStart, steps.length, play]);
|
|
5961
|
+
useEffect(() => {
|
|
5962
|
+
return () => {
|
|
5963
|
+
if (motionRef.current) {
|
|
5964
|
+
cancelAnimationFrame(motionRef.current);
|
|
5965
|
+
}
|
|
5966
|
+
};
|
|
5967
|
+
}, []);
|
|
5968
|
+
return {
|
|
5969
|
+
// 상태
|
|
5970
|
+
isPlaying: state.isPlaying,
|
|
5971
|
+
isPaused: state.isPaused,
|
|
5972
|
+
currentTime: state.currentTime,
|
|
5973
|
+
progress: state.progress,
|
|
5974
|
+
currentStep: state.currentStep,
|
|
5975
|
+
loopCount: state.loopCount,
|
|
5976
|
+
error: state.error,
|
|
5977
|
+
// 제어
|
|
5978
|
+
play,
|
|
5979
|
+
pause,
|
|
5980
|
+
stop,
|
|
5981
|
+
reset,
|
|
5982
|
+
seek,
|
|
5983
|
+
setSpeed,
|
|
5984
|
+
reverse: reverseDirection,
|
|
5985
|
+
// 타임라인 관리
|
|
5986
|
+
addStep,
|
|
5987
|
+
removeStep,
|
|
5988
|
+
updateStep,
|
|
5989
|
+
reorderSteps,
|
|
5990
|
+
// 유틸리티
|
|
5991
|
+
getStepProgress,
|
|
5992
|
+
getStepTime,
|
|
5993
|
+
getTotalDuration
|
|
5994
|
+
};
|
|
5995
|
+
}
|
|
5996
|
+
function useSequence(sequence, options = {}) {
|
|
5997
|
+
const { autoStart = true, loop = false } = options;
|
|
5998
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
5999
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
6000
|
+
const motionsRef = useRef([]);
|
|
6001
|
+
const timeoutsRef = useRef([]);
|
|
6002
|
+
const motions = sequence.map((item) => item.hook());
|
|
6003
|
+
const start = useCallback(() => {
|
|
6004
|
+
if (isPlaying) return;
|
|
6005
|
+
setIsPlaying(true);
|
|
6006
|
+
setCurrentIndex(0);
|
|
6007
|
+
motionsRef.current = motions;
|
|
6008
|
+
if (motionsRef.current[0]) {
|
|
6009
|
+
motionsRef.current[0].start();
|
|
6010
|
+
}
|
|
6011
|
+
sequence.forEach((item, index) => {
|
|
6012
|
+
if (index === 0) return;
|
|
6013
|
+
const timeout = window.setTimeout(() => {
|
|
6014
|
+
if (motionsRef.current[index]) {
|
|
6015
|
+
motionsRef.current[index].start();
|
|
6016
|
+
setCurrentIndex(index);
|
|
6017
|
+
}
|
|
6018
|
+
}, item.delay || 0);
|
|
6019
|
+
timeoutsRef.current.push(timeout);
|
|
6020
|
+
});
|
|
6021
|
+
const totalDuration = sequence.reduce((total, item) => {
|
|
6022
|
+
return total + (item.delay || 0);
|
|
6023
|
+
}, 0);
|
|
6024
|
+
const finalTimeout = window.setTimeout(() => {
|
|
6025
|
+
setIsPlaying(false);
|
|
6026
|
+
if (loop) {
|
|
6027
|
+
start();
|
|
6028
|
+
}
|
|
6029
|
+
}, totalDuration + 1e3);
|
|
6030
|
+
timeoutsRef.current.push(finalTimeout);
|
|
6031
|
+
}, [sequence, isPlaying, loop, motions]);
|
|
6032
|
+
const stop = useCallback(() => {
|
|
6033
|
+
setIsPlaying(false);
|
|
6034
|
+
setCurrentIndex(0);
|
|
6035
|
+
timeoutsRef.current.forEach((timeout) => window.clearTimeout(timeout));
|
|
6036
|
+
timeoutsRef.current = [];
|
|
6037
|
+
motionsRef.current.forEach((motion) => {
|
|
6038
|
+
if (motion && motion.stop) {
|
|
6039
|
+
motion.stop();
|
|
6040
|
+
}
|
|
6041
|
+
});
|
|
6042
|
+
}, []);
|
|
6043
|
+
const reset = useCallback(() => {
|
|
6044
|
+
stop();
|
|
6045
|
+
setCurrentIndex(0);
|
|
6046
|
+
motionsRef.current.forEach((motion) => {
|
|
6047
|
+
if (motion && motion.reset) {
|
|
6048
|
+
motion.reset();
|
|
6049
|
+
}
|
|
6050
|
+
});
|
|
6051
|
+
}, [stop]);
|
|
6052
|
+
const pause = useCallback(() => {
|
|
6053
|
+
setIsPlaying(false);
|
|
6054
|
+
if (motionsRef.current[currentIndex] && motionsRef.current[currentIndex].pause) {
|
|
6055
|
+
motionsRef.current[currentIndex].pause();
|
|
6056
|
+
}
|
|
6057
|
+
}, [currentIndex]);
|
|
6058
|
+
const resume = useCallback(() => {
|
|
6059
|
+
setIsPlaying(true);
|
|
6060
|
+
if (motionsRef.current[currentIndex] && motionsRef.current[currentIndex].resume) {
|
|
6061
|
+
motionsRef.current[currentIndex].resume();
|
|
6062
|
+
}
|
|
6063
|
+
}, [currentIndex]);
|
|
6064
|
+
useEffect(() => {
|
|
6065
|
+
if (autoStart && !isPlaying) {
|
|
6066
|
+
start();
|
|
6067
|
+
}
|
|
6068
|
+
}, [autoStart, isPlaying, start]);
|
|
6069
|
+
return {
|
|
6070
|
+
start,
|
|
6071
|
+
stop,
|
|
6072
|
+
pause,
|
|
6073
|
+
resume,
|
|
6074
|
+
reset,
|
|
6075
|
+
isPlaying,
|
|
6076
|
+
currentIndex,
|
|
6077
|
+
totalMotions: sequence.length,
|
|
6078
|
+
ref: motions[0]?.ref ?? { current: null }
|
|
6079
|
+
// 첫 번째 모션의 ref 반환
|
|
6080
|
+
};
|
|
6081
|
+
}
|
|
6082
|
+
function useLayoutMotion(config) {
|
|
6083
|
+
const {
|
|
6084
|
+
from,
|
|
6085
|
+
to,
|
|
6086
|
+
duration = 500,
|
|
6087
|
+
easing = "ease-in-out",
|
|
6088
|
+
autoStart = false,
|
|
6089
|
+
onComplete
|
|
6090
|
+
} = config;
|
|
6091
|
+
const ref = useRef(null);
|
|
6092
|
+
const [state, setState] = useState({
|
|
6093
|
+
isAnimating: false,
|
|
6094
|
+
progress: 0,
|
|
6095
|
+
currentStyle: {}
|
|
6096
|
+
});
|
|
6097
|
+
const motionFrameRef = useRef(null);
|
|
6098
|
+
const startTimeRef = useRef(0);
|
|
6099
|
+
const parseValue = useCallback(
|
|
6100
|
+
(value) => {
|
|
6101
|
+
if (typeof value === "number") return value;
|
|
6102
|
+
if (typeof value === "string") {
|
|
6103
|
+
const match = value.match(/^(\d+(?:\.\d+)?)(px|%|em|rem|vh|vw)?$/);
|
|
6104
|
+
return match ? parseFloat(match[1]) : 0;
|
|
6105
|
+
}
|
|
6106
|
+
return 0;
|
|
6107
|
+
},
|
|
6108
|
+
[]
|
|
6109
|
+
);
|
|
6110
|
+
const interpolate = useCallback(
|
|
6111
|
+
(from2, to2, progress) => {
|
|
6112
|
+
return from2 + (to2 - from2) * progress;
|
|
6113
|
+
},
|
|
6114
|
+
[]
|
|
6115
|
+
);
|
|
6116
|
+
const applyEasing2 = useCallback(
|
|
6117
|
+
(t) => {
|
|
6118
|
+
switch (easing) {
|
|
6119
|
+
case "ease-in":
|
|
6120
|
+
return t * t;
|
|
6121
|
+
case "ease-out":
|
|
6122
|
+
return 1 - (1 - t) * (1 - t);
|
|
6123
|
+
case "ease-in-out":
|
|
6124
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
6125
|
+
default:
|
|
6126
|
+
return t;
|
|
6127
|
+
}
|
|
6128
|
+
},
|
|
6129
|
+
[easing]
|
|
6130
|
+
);
|
|
6131
|
+
const calculateStyle = useCallback(
|
|
6132
|
+
(progress) => {
|
|
6133
|
+
const easedProgress = applyEasing2(progress);
|
|
6134
|
+
const style = {};
|
|
6135
|
+
if (from.width !== void 0 && to.width !== void 0) {
|
|
6136
|
+
const fromWidth = parseValue(from.width);
|
|
6137
|
+
const toWidth = parseValue(to.width);
|
|
6138
|
+
style.width = `${interpolate(fromWidth, toWidth, easedProgress)}px`;
|
|
6139
|
+
}
|
|
6140
|
+
if (from.height !== void 0 && to.height !== void 0) {
|
|
6141
|
+
const fromHeight = parseValue(from.height);
|
|
6142
|
+
const toHeight = parseValue(to.height);
|
|
6143
|
+
style.height = `${interpolate(fromHeight, toHeight, easedProgress)}px`;
|
|
6144
|
+
}
|
|
6145
|
+
if (from.flexDirection !== to.flexDirection) {
|
|
6146
|
+
style.flexDirection = progress < 0.5 ? from.flexDirection : to.flexDirection;
|
|
6147
|
+
}
|
|
6148
|
+
if (from.justifyContent !== to.justifyContent) {
|
|
6149
|
+
style.justifyContent = progress < 0.5 ? from.justifyContent : to.justifyContent;
|
|
6150
|
+
}
|
|
6151
|
+
if (from.alignItems !== to.alignItems) {
|
|
6152
|
+
style.alignItems = progress < 0.5 ? from.alignItems : to.alignItems;
|
|
6153
|
+
}
|
|
6154
|
+
if (from.gap !== void 0 && to.gap !== void 0) {
|
|
6155
|
+
const fromGap = parseValue(from.gap);
|
|
6156
|
+
const toGap = parseValue(to.gap);
|
|
6157
|
+
style.gap = `${interpolate(fromGap, toGap, easedProgress)}px`;
|
|
6158
|
+
}
|
|
6159
|
+
if (from.gridTemplateColumns !== to.gridTemplateColumns) {
|
|
6160
|
+
style.gridTemplateColumns = progress < 0.5 ? from.gridTemplateColumns : to.gridTemplateColumns;
|
|
6161
|
+
}
|
|
6162
|
+
if (from.gridTemplateRows !== to.gridTemplateRows) {
|
|
6163
|
+
style.gridTemplateRows = progress < 0.5 ? from.gridTemplateRows : to.gridTemplateRows;
|
|
6164
|
+
}
|
|
6165
|
+
if (from.gridGap !== void 0 && to.gridGap !== void 0) {
|
|
6166
|
+
const fromGridGap = parseValue(from.gridGap);
|
|
6167
|
+
const toGridGap = parseValue(to.gridGap);
|
|
6168
|
+
style.gridGap = `${interpolate(fromGridGap, toGridGap, easedProgress)}px`;
|
|
6169
|
+
}
|
|
6170
|
+
return style;
|
|
6171
|
+
},
|
|
6172
|
+
[from, to, applyEasing2, parseValue, interpolate]
|
|
6173
|
+
);
|
|
6174
|
+
const updateMotion = useCallback(() => {
|
|
6175
|
+
if (!state.isAnimating) return;
|
|
6176
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
6177
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
6178
|
+
setState((prev) => ({
|
|
6179
|
+
...prev,
|
|
6180
|
+
progress,
|
|
6181
|
+
currentStyle: calculateStyle(progress)
|
|
6182
|
+
}));
|
|
6183
|
+
if (progress < 1) {
|
|
6184
|
+
motionFrameRef.current = requestAnimationFrame(updateMotion);
|
|
6185
|
+
} else {
|
|
6186
|
+
setState((prev) => ({ ...prev, isAnimating: false }));
|
|
6187
|
+
onComplete?.();
|
|
6188
|
+
}
|
|
6189
|
+
}, [state.isAnimating, duration, calculateStyle, onComplete]);
|
|
6190
|
+
const start = useCallback(() => {
|
|
6191
|
+
setState((prev) => ({ ...prev, isAnimating: true, progress: 0 }));
|
|
6192
|
+
startTimeRef.current = Date.now();
|
|
6193
|
+
updateMotion();
|
|
6194
|
+
}, [updateMotion]);
|
|
6195
|
+
const stop = useCallback(() => {
|
|
6196
|
+
setState((prev) => ({ ...prev, isAnimating: false }));
|
|
6197
|
+
if (motionFrameRef.current) {
|
|
6198
|
+
cancelAnimationFrame(motionFrameRef.current);
|
|
6199
|
+
}
|
|
6200
|
+
}, []);
|
|
6201
|
+
const reset = useCallback(() => {
|
|
6202
|
+
setState({
|
|
6203
|
+
isAnimating: false,
|
|
6204
|
+
progress: 0,
|
|
6205
|
+
currentStyle: calculateStyle(0)
|
|
6206
|
+
});
|
|
6207
|
+
if (motionFrameRef.current) {
|
|
6208
|
+
cancelAnimationFrame(motionFrameRef.current);
|
|
6209
|
+
}
|
|
6210
|
+
}, [calculateStyle]);
|
|
6211
|
+
useEffect(() => {
|
|
6212
|
+
if (autoStart) {
|
|
6213
|
+
start();
|
|
6214
|
+
}
|
|
6215
|
+
return () => {
|
|
6216
|
+
if (motionFrameRef.current) {
|
|
6217
|
+
cancelAnimationFrame(motionFrameRef.current);
|
|
6218
|
+
}
|
|
6219
|
+
};
|
|
6220
|
+
}, [autoStart, start]);
|
|
6221
|
+
return {
|
|
6222
|
+
ref,
|
|
6223
|
+
state,
|
|
6224
|
+
start,
|
|
6225
|
+
stop,
|
|
6226
|
+
reset
|
|
6227
|
+
};
|
|
6228
|
+
}
|
|
6229
|
+
function createLayoutTransition(from, to, options = {}) {
|
|
6230
|
+
return {
|
|
6231
|
+
from,
|
|
6232
|
+
to,
|
|
6233
|
+
duration: options.duration || 500,
|
|
6234
|
+
easing: options.easing || "ease-in-out",
|
|
6235
|
+
autoStart: options.autoStart || false,
|
|
6236
|
+
onComplete: options.onComplete
|
|
6237
|
+
};
|
|
6238
|
+
}
|
|
6239
|
+
function useKeyboardToggle(options = {}) {
|
|
6240
|
+
const {
|
|
6241
|
+
initialState = false,
|
|
6242
|
+
keys = [" "],
|
|
6243
|
+
keyCode,
|
|
6244
|
+
keyCombo,
|
|
6245
|
+
toggleOnKeyDown = true,
|
|
6246
|
+
toggleOnKeyUp = false,
|
|
6247
|
+
toggleOnKeyPress = false,
|
|
6248
|
+
autoReset = false,
|
|
6249
|
+
resetDelay = 3e3,
|
|
6250
|
+
preventDefault = false,
|
|
6251
|
+
stopPropagation = false,
|
|
6252
|
+
requireFocus = false,
|
|
6253
|
+
showOnMount = false
|
|
6254
|
+
} = options;
|
|
6255
|
+
const [isActive, setIsActive] = useState(showOnMount ? initialState : false);
|
|
6256
|
+
const [mounted, setMounted] = useState(false);
|
|
6257
|
+
const resetTimeoutRef = useRef(null);
|
|
6258
|
+
const elementRef = useRef(null);
|
|
6259
|
+
const pressedKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
6260
|
+
useEffect(() => {
|
|
6261
|
+
setMounted(true);
|
|
6262
|
+
}, []);
|
|
6263
|
+
const startResetTimer = useCallback(() => {
|
|
6264
|
+
if (!autoReset || resetDelay <= 0) return;
|
|
6265
|
+
if (resetTimeoutRef.current !== null) {
|
|
6266
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6267
|
+
}
|
|
6268
|
+
resetTimeoutRef.current = window.setTimeout(() => {
|
|
6269
|
+
setIsActive(false);
|
|
6270
|
+
resetTimeoutRef.current = null;
|
|
6271
|
+
}, resetDelay);
|
|
6272
|
+
}, [autoReset, resetDelay]);
|
|
6273
|
+
const toggle = useCallback(() => {
|
|
6274
|
+
if (!mounted) return;
|
|
6275
|
+
setIsActive((prev) => {
|
|
6276
|
+
const newState = !prev;
|
|
6277
|
+
if (newState && autoReset) {
|
|
6278
|
+
startResetTimer();
|
|
6279
|
+
} else if (!newState && resetTimeoutRef.current !== null) {
|
|
6280
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6281
|
+
resetTimeoutRef.current = null;
|
|
6282
|
+
}
|
|
6283
|
+
return newState;
|
|
6284
|
+
});
|
|
6285
|
+
}, [mounted, autoReset, startResetTimer]);
|
|
6286
|
+
const activate = useCallback(() => {
|
|
6287
|
+
if (!mounted || isActive) return;
|
|
6288
|
+
setIsActive(true);
|
|
6289
|
+
if (autoReset) {
|
|
6290
|
+
startResetTimer();
|
|
6291
|
+
}
|
|
6292
|
+
}, [mounted, isActive, autoReset, startResetTimer]);
|
|
6293
|
+
const deactivate = useCallback(() => {
|
|
6294
|
+
if (!mounted || !isActive) return;
|
|
6295
|
+
setIsActive(false);
|
|
6296
|
+
if (resetTimeoutRef.current !== null) {
|
|
6297
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6298
|
+
resetTimeoutRef.current = null;
|
|
6299
|
+
}
|
|
6300
|
+
}, [mounted, isActive]);
|
|
6301
|
+
const reset = useCallback(() => {
|
|
6302
|
+
setIsActive(initialState);
|
|
6303
|
+
if (resetTimeoutRef.current !== null) {
|
|
6304
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6305
|
+
resetTimeoutRef.current = null;
|
|
6306
|
+
}
|
|
6307
|
+
}, [initialState]);
|
|
6308
|
+
const isKeyMatch = useCallback(
|
|
6309
|
+
(event) => {
|
|
6310
|
+
if (keyCode !== void 0 && event.keyCode === keyCode) {
|
|
6311
|
+
return true;
|
|
6312
|
+
}
|
|
6313
|
+
if (keys.length > 0 && keys.includes(event.key)) {
|
|
6314
|
+
return true;
|
|
6315
|
+
}
|
|
6316
|
+
if (keyCombo && keyCombo.length > 0) {
|
|
6317
|
+
const pressedKeys = Array.from(pressedKeysRef.current);
|
|
6318
|
+
const comboMatch = keyCombo.every((key) => pressedKeys.includes(key));
|
|
6319
|
+
return comboMatch;
|
|
6320
|
+
}
|
|
6321
|
+
return false;
|
|
6322
|
+
},
|
|
6323
|
+
[keys, keyCode, keyCombo]
|
|
6324
|
+
);
|
|
6325
|
+
const isFocused = useCallback(() => {
|
|
6326
|
+
if (!requireFocus) return true;
|
|
6327
|
+
return document.activeElement === elementRef.current;
|
|
6328
|
+
}, [requireFocus]);
|
|
6329
|
+
const handleKeyDown = useCallback(
|
|
6330
|
+
(event) => {
|
|
6331
|
+
if (!toggleOnKeyDown || !isFocused()) return;
|
|
6332
|
+
pressedKeysRef.current.add(event.key);
|
|
6333
|
+
if (isKeyMatch(event)) {
|
|
6334
|
+
if (preventDefault) event.preventDefault();
|
|
6335
|
+
if (stopPropagation) event.stopPropagation();
|
|
6336
|
+
toggle();
|
|
6337
|
+
}
|
|
6338
|
+
},
|
|
6339
|
+
[
|
|
6340
|
+
toggleOnKeyDown,
|
|
6341
|
+
isFocused,
|
|
6342
|
+
isKeyMatch,
|
|
6343
|
+
preventDefault,
|
|
6344
|
+
stopPropagation,
|
|
6345
|
+
toggle
|
|
6346
|
+
]
|
|
6347
|
+
);
|
|
6348
|
+
const handleKeyUp = useCallback(
|
|
6349
|
+
(event) => {
|
|
6350
|
+
if (!toggleOnKeyUp || !isFocused()) return;
|
|
6351
|
+
pressedKeysRef.current.delete(event.key);
|
|
6352
|
+
if (isKeyMatch(event)) {
|
|
6353
|
+
if (preventDefault) event.preventDefault();
|
|
6354
|
+
if (stopPropagation) event.stopPropagation();
|
|
6355
|
+
toggle();
|
|
6356
|
+
}
|
|
6357
|
+
},
|
|
6358
|
+
[
|
|
6359
|
+
toggleOnKeyUp,
|
|
6360
|
+
isFocused,
|
|
6361
|
+
isKeyMatch,
|
|
6362
|
+
preventDefault,
|
|
6363
|
+
stopPropagation,
|
|
6364
|
+
toggle
|
|
6365
|
+
]
|
|
6366
|
+
);
|
|
6367
|
+
const handleKeyPress = useCallback(
|
|
6368
|
+
(event) => {
|
|
6369
|
+
if (!toggleOnKeyPress || !isFocused()) return;
|
|
6370
|
+
if (isKeyMatch(event)) {
|
|
6371
|
+
if (preventDefault) event.preventDefault();
|
|
6372
|
+
if (stopPropagation) event.stopPropagation();
|
|
6373
|
+
toggle();
|
|
6374
|
+
}
|
|
6375
|
+
},
|
|
6376
|
+
[
|
|
6377
|
+
toggleOnKeyPress,
|
|
6378
|
+
isFocused,
|
|
6379
|
+
isKeyMatch,
|
|
6380
|
+
preventDefault,
|
|
6381
|
+
stopPropagation,
|
|
6382
|
+
toggle
|
|
6383
|
+
]
|
|
6384
|
+
);
|
|
6385
|
+
useEffect(() => {
|
|
6386
|
+
if (requireFocus || !mounted) return;
|
|
6387
|
+
const handleGlobalKeyDown = (event) => {
|
|
6388
|
+
pressedKeysRef.current.add(event.key);
|
|
6389
|
+
if (isKeyMatch(event)) {
|
|
6390
|
+
if (preventDefault) event.preventDefault();
|
|
6391
|
+
if (stopPropagation) event.stopPropagation();
|
|
6392
|
+
toggle();
|
|
6393
|
+
}
|
|
6394
|
+
};
|
|
6395
|
+
const handleGlobalKeyUp = (event) => {
|
|
6396
|
+
pressedKeysRef.current.delete(event.key);
|
|
6397
|
+
if (isKeyMatch(event)) {
|
|
6398
|
+
if (preventDefault) event.preventDefault();
|
|
6399
|
+
if (stopPropagation) event.stopPropagation();
|
|
6400
|
+
toggle();
|
|
6401
|
+
}
|
|
6402
|
+
};
|
|
6403
|
+
document.addEventListener("keydown", handleGlobalKeyDown);
|
|
6404
|
+
document.addEventListener("keyup", handleGlobalKeyUp);
|
|
6405
|
+
return () => {
|
|
6406
|
+
document.removeEventListener("keydown", handleGlobalKeyDown);
|
|
6407
|
+
document.removeEventListener("keyup", handleGlobalKeyUp);
|
|
6408
|
+
};
|
|
6409
|
+
}, [
|
|
6410
|
+
requireFocus,
|
|
6411
|
+
mounted,
|
|
6412
|
+
isKeyMatch,
|
|
6413
|
+
preventDefault,
|
|
6414
|
+
stopPropagation,
|
|
6415
|
+
toggle
|
|
6416
|
+
]);
|
|
6417
|
+
useEffect(() => {
|
|
6418
|
+
return () => {
|
|
6419
|
+
if (resetTimeoutRef.current !== null) {
|
|
6420
|
+
clearTimeout(resetTimeoutRef.current);
|
|
6421
|
+
}
|
|
6422
|
+
};
|
|
6423
|
+
}, []);
|
|
6424
|
+
const keyboardHandlers = {
|
|
6425
|
+
...toggleOnKeyDown && { onKeyDown: handleKeyDown },
|
|
6426
|
+
...toggleOnKeyUp && { onKeyUp: handleKeyUp },
|
|
6427
|
+
...toggleOnKeyPress && { onKeyPress: handleKeyPress }
|
|
6428
|
+
};
|
|
6429
|
+
return {
|
|
6430
|
+
isActive,
|
|
6431
|
+
mounted,
|
|
6432
|
+
toggle,
|
|
6433
|
+
activate,
|
|
6434
|
+
deactivate,
|
|
6435
|
+
reset,
|
|
6436
|
+
keyboardHandlers,
|
|
6437
|
+
ref: elementRef
|
|
6438
|
+
};
|
|
6439
|
+
}
|
|
6440
|
+
function useScrollDirection(options = {}) {
|
|
6441
|
+
const { threshold = 10, idleDelay = 150, showOnMount = false } = options;
|
|
6442
|
+
const [direction, setDirection] = useState(
|
|
6443
|
+
showOnMount ? "idle" : "idle"
|
|
6444
|
+
);
|
|
6445
|
+
const [mounted, setMounted] = useState(false);
|
|
6446
|
+
const [lastScrollY, setLastScrollY] = useState(0);
|
|
6447
|
+
const [idleTimeout, setIdleTimeout] = useState(null);
|
|
6448
|
+
useEffect(() => {
|
|
6449
|
+
setMounted(true);
|
|
6450
|
+
}, []);
|
|
6451
|
+
useEffect(() => {
|
|
6452
|
+
if (!mounted) return;
|
|
6453
|
+
const handleScroll = () => {
|
|
6454
|
+
if (typeof window !== "undefined") {
|
|
6455
|
+
const currentScrollY = window.pageYOffset;
|
|
6456
|
+
const scrollDifference = Math.abs(currentScrollY - lastScrollY);
|
|
6457
|
+
if (idleTimeout !== null) {
|
|
6458
|
+
clearTimeout(idleTimeout);
|
|
6459
|
+
}
|
|
6460
|
+
if (scrollDifference > threshold) {
|
|
6461
|
+
const newDirection = currentScrollY > lastScrollY ? "down" : "up";
|
|
6462
|
+
setDirection(newDirection);
|
|
6463
|
+
setLastScrollY(currentScrollY);
|
|
6464
|
+
const timeout = setTimeout(() => {
|
|
6465
|
+
setDirection("idle");
|
|
6466
|
+
}, idleDelay);
|
|
6467
|
+
setIdleTimeout(timeout);
|
|
6468
|
+
}
|
|
6469
|
+
}
|
|
6470
|
+
};
|
|
6471
|
+
if (typeof window !== "undefined") {
|
|
6472
|
+
setLastScrollY(window.pageYOffset);
|
|
6473
|
+
}
|
|
6474
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
6475
|
+
return () => {
|
|
6476
|
+
window.removeEventListener("scroll", handleScroll);
|
|
6477
|
+
if (idleTimeout !== null) {
|
|
6478
|
+
clearTimeout(idleTimeout);
|
|
6479
|
+
}
|
|
6480
|
+
};
|
|
6481
|
+
}, [threshold, idleDelay, mounted, lastScrollY, idleTimeout]);
|
|
6482
|
+
return {
|
|
6483
|
+
direction,
|
|
6484
|
+
mounted
|
|
6485
|
+
};
|
|
6486
|
+
}
|
|
6487
|
+
function useStickyToggle(options = {}) {
|
|
6488
|
+
const {
|
|
6489
|
+
offset = 0,
|
|
6490
|
+
behavior: _behavior = "smooth",
|
|
6491
|
+
showOnMount = false
|
|
6492
|
+
} = options;
|
|
6493
|
+
const [isSticky, setIsSticky] = useState(showOnMount);
|
|
6494
|
+
const [mounted, setMounted] = useState(false);
|
|
6495
|
+
useEffect(() => {
|
|
6496
|
+
setMounted(true);
|
|
6497
|
+
}, []);
|
|
6498
|
+
useEffect(() => {
|
|
6499
|
+
if (!mounted) return;
|
|
6500
|
+
const toggleSticky = () => {
|
|
6501
|
+
if (typeof window !== "undefined") {
|
|
6502
|
+
if (window.pageYOffset > offset) {
|
|
6503
|
+
setIsSticky(true);
|
|
6504
|
+
} else {
|
|
6505
|
+
setIsSticky(false);
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
};
|
|
6509
|
+
toggleSticky();
|
|
6510
|
+
window.addEventListener("scroll", toggleSticky, { passive: true });
|
|
6511
|
+
window.addEventListener("resize", toggleSticky, { passive: true });
|
|
6512
|
+
return () => {
|
|
6513
|
+
window.removeEventListener("scroll", toggleSticky);
|
|
6514
|
+
window.removeEventListener("resize", toggleSticky);
|
|
6515
|
+
};
|
|
6516
|
+
}, [offset, mounted]);
|
|
6517
|
+
return {
|
|
6518
|
+
isSticky,
|
|
6519
|
+
mounted
|
|
6520
|
+
};
|
|
6521
|
+
}
|
|
6522
|
+
function useInteractive(config = {}) {
|
|
6523
|
+
const {
|
|
6524
|
+
hoverScale = 1.05,
|
|
6525
|
+
clickScale = 0.95,
|
|
6526
|
+
duration: _duration = 200
|
|
6527
|
+
} = config;
|
|
6528
|
+
const ref = useRef(null);
|
|
6529
|
+
const [scale, setScale] = useState(1);
|
|
6530
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
6531
|
+
const [isClicked, setIsClicked] = useState(false);
|
|
6532
|
+
const handleMouseEnter = useCallback(() => {
|
|
6533
|
+
setIsHovered(true);
|
|
6534
|
+
setScale(hoverScale);
|
|
6535
|
+
}, [hoverScale]);
|
|
6536
|
+
const handleMouseLeave = useCallback(() => {
|
|
6537
|
+
setIsHovered(false);
|
|
6538
|
+
setScale(1);
|
|
6539
|
+
}, []);
|
|
6540
|
+
const handleMouseDown = useCallback(() => {
|
|
6541
|
+
setIsClicked(true);
|
|
6542
|
+
setScale(clickScale);
|
|
6543
|
+
}, [clickScale]);
|
|
6544
|
+
const handleMouseUp = useCallback(() => {
|
|
6545
|
+
setIsClicked(false);
|
|
6546
|
+
setScale(isHovered ? hoverScale : 1);
|
|
6547
|
+
}, [isHovered, hoverScale]);
|
|
6548
|
+
const setRef = (element) => {
|
|
6549
|
+
if (ref.current !== element) {
|
|
6550
|
+
ref.current = element;
|
|
6551
|
+
}
|
|
6552
|
+
};
|
|
6553
|
+
return {
|
|
6554
|
+
ref: setRef,
|
|
6555
|
+
scale,
|
|
6556
|
+
isHovered,
|
|
6557
|
+
isClicked,
|
|
6558
|
+
handleMouseEnter,
|
|
6559
|
+
handleMouseLeave,
|
|
6560
|
+
handleMouseDown,
|
|
6561
|
+
handleMouseUp
|
|
6562
|
+
};
|
|
6563
|
+
}
|
|
6564
|
+
function usePerformanceMonitor(config = {}) {
|
|
6565
|
+
const { threshold = 30, onPerformanceIssue } = config;
|
|
6566
|
+
const ref = useRef(null);
|
|
6567
|
+
const [fps, setFps] = useState(60);
|
|
6568
|
+
const [isLowPerformance, setIsLowPerformance] = useState(false);
|
|
6569
|
+
const [frameCount, setFrameCount] = useState(0);
|
|
6570
|
+
const lastTime = useRef(performance.now());
|
|
6571
|
+
const frameCountRef = useRef(0);
|
|
6572
|
+
const measurePerformance = () => {
|
|
6573
|
+
const now = performance.now();
|
|
6574
|
+
frameCountRef.current++;
|
|
6575
|
+
if (now - lastTime.current >= 1e3) {
|
|
6576
|
+
const currentFps = Math.round(
|
|
6577
|
+
frameCountRef.current * 1e3 / (now - lastTime.current)
|
|
6578
|
+
);
|
|
6579
|
+
setFps(currentFps);
|
|
6580
|
+
setFrameCount(frameCountRef.current);
|
|
6581
|
+
const lowPerformance = currentFps < threshold;
|
|
6582
|
+
setIsLowPerformance(lowPerformance);
|
|
6583
|
+
if (lowPerformance && onPerformanceIssue) {
|
|
6584
|
+
onPerformanceIssue(currentFps);
|
|
6585
|
+
}
|
|
6586
|
+
frameCountRef.current = 0;
|
|
6587
|
+
lastTime.current = now;
|
|
6588
|
+
}
|
|
6589
|
+
requestAnimationFrame(measurePerformance);
|
|
6590
|
+
};
|
|
6591
|
+
useEffect(() => {
|
|
6592
|
+
const motionId = requestAnimationFrame(measurePerformance);
|
|
6593
|
+
return () => cancelAnimationFrame(motionId);
|
|
6594
|
+
}, []);
|
|
6595
|
+
const setRef = (element) => {
|
|
6596
|
+
if (ref.current !== element) {
|
|
6597
|
+
ref.current = element;
|
|
6598
|
+
}
|
|
6599
|
+
};
|
|
6600
|
+
return {
|
|
6601
|
+
ref: setRef,
|
|
6602
|
+
fps,
|
|
6603
|
+
isLowPerformance,
|
|
6604
|
+
frameCount
|
|
6605
|
+
};
|
|
6606
|
+
}
|
|
6607
|
+
function useLanguageAwareMotion(options) {
|
|
6608
|
+
const {
|
|
6609
|
+
motionType,
|
|
6610
|
+
duration = 700,
|
|
6611
|
+
delay = 0,
|
|
6612
|
+
threshold = 0.1,
|
|
6613
|
+
pauseOnLanguageChange = true,
|
|
6614
|
+
restartOnLanguageChange = false,
|
|
6615
|
+
currentLanguage: externalLanguage
|
|
6616
|
+
} = options;
|
|
6617
|
+
const elementRef = useRef(null);
|
|
6618
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
6619
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
6620
|
+
const [internalLanguage, setInternalLanguage] = useState("");
|
|
6621
|
+
const isVisibleRef = useRef(false);
|
|
6622
|
+
const isPausedRef = useRef(false);
|
|
6623
|
+
isVisibleRef.current = isVisible;
|
|
6624
|
+
isPausedRef.current = isPaused;
|
|
6625
|
+
useEffect(() => {
|
|
6626
|
+
if (externalLanguage && internalLanguage !== externalLanguage) {
|
|
6627
|
+
setInternalLanguage(externalLanguage);
|
|
6628
|
+
if (pauseOnLanguageChange && isVisible) {
|
|
6629
|
+
setIsPaused(true);
|
|
6630
|
+
setTimeout(() => {
|
|
6631
|
+
setIsPaused(false);
|
|
6632
|
+
}, 200);
|
|
6633
|
+
}
|
|
6634
|
+
if (restartOnLanguageChange && isVisible) {
|
|
6635
|
+
setIsVisible(false);
|
|
6636
|
+
setTimeout(() => {
|
|
6637
|
+
setIsVisible(true);
|
|
6638
|
+
}, 100);
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
}, [
|
|
6642
|
+
externalLanguage,
|
|
6643
|
+
internalLanguage,
|
|
6644
|
+
isVisible,
|
|
6645
|
+
pauseOnLanguageChange,
|
|
6646
|
+
restartOnLanguageChange
|
|
6647
|
+
]);
|
|
6648
|
+
useEffect(() => {
|
|
6649
|
+
if (!elementRef.current) return;
|
|
6650
|
+
const observer = new IntersectionObserver(
|
|
6651
|
+
(entries) => {
|
|
6652
|
+
entries.forEach((entry) => {
|
|
6653
|
+
if (entry.isIntersecting && !isVisibleRef.current && !isPausedRef.current) {
|
|
6654
|
+
setTimeout(() => {
|
|
6655
|
+
setIsVisible(true);
|
|
6656
|
+
}, delay);
|
|
6657
|
+
}
|
|
6658
|
+
});
|
|
6659
|
+
},
|
|
6660
|
+
{ threshold }
|
|
6661
|
+
);
|
|
6662
|
+
observer.observe(elementRef.current);
|
|
6663
|
+
return () => {
|
|
6664
|
+
observer.disconnect();
|
|
6665
|
+
};
|
|
6666
|
+
}, [delay, threshold]);
|
|
6667
|
+
const getMotionStyle = useCallback(() => {
|
|
6668
|
+
const baseTransition = `all ${duration}ms ease-out`;
|
|
6669
|
+
if (isPaused) {
|
|
6670
|
+
return {
|
|
6671
|
+
opacity: 1,
|
|
6672
|
+
transform: "none",
|
|
6673
|
+
transition: baseTransition
|
|
6674
|
+
};
|
|
6675
|
+
}
|
|
6676
|
+
if (!isVisible) {
|
|
6677
|
+
switch (motionType) {
|
|
6678
|
+
case "fadeIn":
|
|
6679
|
+
return {
|
|
6680
|
+
opacity: 0,
|
|
6681
|
+
transition: baseTransition
|
|
6682
|
+
};
|
|
6683
|
+
case "slideUp":
|
|
6684
|
+
return {
|
|
6685
|
+
opacity: 0,
|
|
6686
|
+
transform: "translateY(32px)",
|
|
6687
|
+
transition: baseTransition
|
|
6688
|
+
};
|
|
6689
|
+
case "slideLeft":
|
|
6690
|
+
return {
|
|
6691
|
+
opacity: 0,
|
|
6692
|
+
transform: "translateX(-32px)",
|
|
6693
|
+
transition: baseTransition
|
|
6694
|
+
};
|
|
6695
|
+
case "slideRight":
|
|
6696
|
+
return {
|
|
6697
|
+
opacity: 0,
|
|
6698
|
+
transform: "translateX(32px)",
|
|
6699
|
+
transition: baseTransition
|
|
6700
|
+
};
|
|
6701
|
+
case "scaleIn":
|
|
6702
|
+
return {
|
|
6703
|
+
opacity: 0,
|
|
6704
|
+
transform: "scale(0.95)",
|
|
6705
|
+
transition: baseTransition
|
|
6706
|
+
};
|
|
6707
|
+
case "bounceIn":
|
|
6708
|
+
return {
|
|
6709
|
+
opacity: 0,
|
|
6710
|
+
transform: "scale(0.75)",
|
|
6711
|
+
transition: baseTransition
|
|
6712
|
+
};
|
|
6713
|
+
default:
|
|
6714
|
+
return {
|
|
6715
|
+
opacity: 0,
|
|
6716
|
+
transition: baseTransition
|
|
6717
|
+
};
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
return {
|
|
6721
|
+
opacity: 1,
|
|
6722
|
+
transform: "none",
|
|
6723
|
+
transition: baseTransition
|
|
6724
|
+
};
|
|
6725
|
+
}, [isVisible, isPaused, motionType, duration]);
|
|
6726
|
+
const pauseMotion = useCallback(() => {
|
|
6727
|
+
setIsPaused(true);
|
|
6728
|
+
}, []);
|
|
6729
|
+
const resumeMotion = useCallback(() => {
|
|
6730
|
+
setIsPaused(false);
|
|
6731
|
+
}, []);
|
|
6732
|
+
const restartMotion = useCallback(() => {
|
|
6733
|
+
setIsVisible(false);
|
|
6734
|
+
setTimeout(() => {
|
|
6735
|
+
setIsVisible(true);
|
|
6736
|
+
}, 100);
|
|
6737
|
+
}, []);
|
|
6738
|
+
const motionStyle = useMemo(() => getMotionStyle(), [getMotionStyle]);
|
|
6739
|
+
return {
|
|
6740
|
+
ref: elementRef,
|
|
6741
|
+
isVisible,
|
|
6742
|
+
isPaused,
|
|
6743
|
+
style: motionStyle,
|
|
6744
|
+
pauseMotion,
|
|
6745
|
+
resumeMotion,
|
|
6746
|
+
restartMotion,
|
|
6747
|
+
currentLanguage: internalLanguage
|
|
6748
|
+
};
|
|
6749
|
+
}
|
|
6750
|
+
function useGameLoop(options = {}) {
|
|
6751
|
+
const {
|
|
6752
|
+
fps = 60,
|
|
6753
|
+
autoStart = false,
|
|
6754
|
+
maxFPS = 120,
|
|
6755
|
+
minFPS = 30,
|
|
6756
|
+
showOnMount = false
|
|
6757
|
+
} = options;
|
|
6758
|
+
const [isRunning, setIsRunning] = useState(showOnMount ? autoStart : false);
|
|
6759
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
6760
|
+
const [currentFPS, setCurrentFPS] = useState(0);
|
|
6761
|
+
const [deltaTime, setDeltaTime] = useState(0);
|
|
6762
|
+
const [elapsedTime, setElapsedTime] = useState(0);
|
|
6763
|
+
const [frameCount, setFrameCount] = useState(0);
|
|
6764
|
+
const [mounted, setMounted] = useState(false);
|
|
6765
|
+
const motionRef = useRef(null);
|
|
6766
|
+
const lastTimeRef = useRef(null);
|
|
6767
|
+
const frameTimeRef = useRef(1e3 / fps);
|
|
6768
|
+
const fpsUpdateTimeRef = useRef(0);
|
|
6769
|
+
const fpsFrameCountRef = useRef(0);
|
|
6770
|
+
const updateCallbacksRef = useRef([]);
|
|
6771
|
+
const renderCallbacksRef = useRef([]);
|
|
6772
|
+
useEffect(() => {
|
|
6773
|
+
setMounted(true);
|
|
6774
|
+
}, []);
|
|
6775
|
+
const gameLoop = useCallback(
|
|
6776
|
+
(currentTime) => {
|
|
6777
|
+
if (!isRunning || isPaused) return;
|
|
6778
|
+
if (lastTimeRef.current === null) {
|
|
6779
|
+
lastTimeRef.current = currentTime;
|
|
6780
|
+
motionRef.current = requestAnimationFrame(gameLoop);
|
|
6781
|
+
return;
|
|
6782
|
+
}
|
|
6783
|
+
const delta = currentTime - lastTimeRef.current;
|
|
6784
|
+
const targetDelta = frameTimeRef.current;
|
|
6785
|
+
if (delta >= targetDelta) {
|
|
6786
|
+
updateCallbacksRef.current.forEach((callback) => {
|
|
6787
|
+
try {
|
|
6788
|
+
callback(delta, elapsedTime);
|
|
6789
|
+
} catch (error) {
|
|
6790
|
+
if (process.env.NODE_ENV === "development") {
|
|
6791
|
+
console.error("Game loop update error:", error);
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
});
|
|
6795
|
+
renderCallbacksRef.current.forEach((callback) => {
|
|
6796
|
+
try {
|
|
6797
|
+
callback(delta, elapsedTime);
|
|
6798
|
+
} catch (error) {
|
|
6799
|
+
if (process.env.NODE_ENV === "development") {
|
|
6800
|
+
console.error("Game loop render error:", error);
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
});
|
|
6804
|
+
setDeltaTime(delta);
|
|
6805
|
+
setElapsedTime((prev) => prev + delta);
|
|
6806
|
+
setFrameCount((prev) => prev + 1);
|
|
6807
|
+
lastTimeRef.current = currentTime;
|
|
6808
|
+
fpsFrameCountRef.current++;
|
|
6809
|
+
if (currentTime - fpsUpdateTimeRef.current >= 1e3) {
|
|
6810
|
+
const newFPS = Math.round(
|
|
6811
|
+
fpsFrameCountRef.current * 1e3 / (currentTime - fpsUpdateTimeRef.current)
|
|
6812
|
+
);
|
|
6813
|
+
setCurrentFPS(newFPS);
|
|
6814
|
+
fpsFrameCountRef.current = 0;
|
|
6815
|
+
fpsUpdateTimeRef.current = currentTime;
|
|
6816
|
+
if (process.env.NODE_ENV === "development" && typeof window !== "undefined" && window.location.hostname === "localhost") {
|
|
6817
|
+
if (newFPS < minFPS) {
|
|
6818
|
+
console.warn(`Low FPS detected: ${newFPS} (min: ${minFPS})`);
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
motionRef.current = requestAnimationFrame(gameLoop);
|
|
6824
|
+
},
|
|
6825
|
+
[isRunning, isPaused, elapsedTime, minFPS]
|
|
6826
|
+
);
|
|
6827
|
+
const start = useCallback(() => {
|
|
6828
|
+
if (!mounted) return;
|
|
6829
|
+
setIsRunning(true);
|
|
6830
|
+
setIsPaused(false);
|
|
6831
|
+
setElapsedTime(0);
|
|
6832
|
+
setFrameCount(0);
|
|
6833
|
+
setDeltaTime(0);
|
|
6834
|
+
setCurrentFPS(0);
|
|
6835
|
+
lastTimeRef.current = null;
|
|
6836
|
+
fpsUpdateTimeRef.current = 0;
|
|
6837
|
+
fpsFrameCountRef.current = 0;
|
|
6838
|
+
if (!motionRef.current) {
|
|
6839
|
+
motionRef.current = requestAnimationFrame(gameLoop);
|
|
6840
|
+
}
|
|
6841
|
+
}, [mounted, gameLoop]);
|
|
6842
|
+
const stop = useCallback(() => {
|
|
6843
|
+
setIsRunning(false);
|
|
6844
|
+
setIsPaused(false);
|
|
6845
|
+
if (motionRef.current) {
|
|
6846
|
+
cancelAnimationFrame(motionRef.current);
|
|
6847
|
+
motionRef.current = null;
|
|
6848
|
+
}
|
|
6849
|
+
}, []);
|
|
6850
|
+
const pause = useCallback(() => {
|
|
6851
|
+
if (!isRunning) return;
|
|
6852
|
+
setIsPaused(true);
|
|
6853
|
+
}, [isRunning]);
|
|
6854
|
+
const resume = useCallback(() => {
|
|
6855
|
+
if (!isRunning) return;
|
|
6856
|
+
setIsPaused(false);
|
|
6857
|
+
if (!motionRef.current) {
|
|
6858
|
+
motionRef.current = requestAnimationFrame(gameLoop);
|
|
6859
|
+
}
|
|
6860
|
+
}, [isRunning, gameLoop]);
|
|
6861
|
+
const reset = useCallback(() => {
|
|
6862
|
+
setElapsedTime(0);
|
|
6863
|
+
setFrameCount(0);
|
|
6864
|
+
setDeltaTime(0);
|
|
6865
|
+
setCurrentFPS(0);
|
|
6866
|
+
lastTimeRef.current = null;
|
|
6867
|
+
fpsUpdateTimeRef.current = 0;
|
|
6868
|
+
fpsFrameCountRef.current = 0;
|
|
6869
|
+
}, []);
|
|
6870
|
+
const onUpdate = useCallback(
|
|
6871
|
+
(callback) => {
|
|
6872
|
+
updateCallbacksRef.current.push(callback);
|
|
6873
|
+
return () => {
|
|
6874
|
+
const index = updateCallbacksRef.current.indexOf(callback);
|
|
6875
|
+
if (index > -1) {
|
|
6876
|
+
updateCallbacksRef.current.splice(index, 1);
|
|
6877
|
+
}
|
|
6878
|
+
};
|
|
6879
|
+
},
|
|
6880
|
+
[]
|
|
6881
|
+
);
|
|
6882
|
+
const onRender = useCallback(
|
|
6883
|
+
(callback) => {
|
|
6884
|
+
renderCallbacksRef.current.push(callback);
|
|
6885
|
+
return () => {
|
|
6886
|
+
const index = renderCallbacksRef.current.indexOf(callback);
|
|
6887
|
+
if (index > -1) {
|
|
6888
|
+
renderCallbacksRef.current.splice(index, 1);
|
|
6889
|
+
}
|
|
6890
|
+
};
|
|
6891
|
+
},
|
|
6892
|
+
[]
|
|
6893
|
+
);
|
|
6894
|
+
useEffect(() => {
|
|
6895
|
+
frameTimeRef.current = 1e3 / Math.min(fps, maxFPS);
|
|
6896
|
+
}, [fps, maxFPS]);
|
|
6897
|
+
useEffect(() => {
|
|
6898
|
+
if (mounted && autoStart && !isRunning) {
|
|
6899
|
+
start();
|
|
6900
|
+
}
|
|
6901
|
+
}, [mounted, autoStart, isRunning, start]);
|
|
6902
|
+
useEffect(() => {
|
|
6903
|
+
return () => {
|
|
6904
|
+
if (motionRef.current) {
|
|
6905
|
+
cancelAnimationFrame(motionRef.current);
|
|
6906
|
+
}
|
|
6907
|
+
};
|
|
6908
|
+
}, []);
|
|
6909
|
+
return {
|
|
6910
|
+
isRunning,
|
|
6911
|
+
fps: currentFPS,
|
|
6912
|
+
deltaTime,
|
|
6913
|
+
elapsedTime,
|
|
6914
|
+
frameCount,
|
|
6915
|
+
mounted,
|
|
6916
|
+
start,
|
|
6917
|
+
stop,
|
|
6918
|
+
pause,
|
|
6919
|
+
resume,
|
|
6920
|
+
reset,
|
|
6921
|
+
onUpdate,
|
|
6922
|
+
onRender
|
|
6923
|
+
};
|
|
6924
|
+
}
|
|
6925
|
+
function useMotion(configOrFrom = {}, to, options) {
|
|
6926
|
+
let config;
|
|
6927
|
+
let fromValues;
|
|
6928
|
+
let toValues;
|
|
6929
|
+
if (to && options) {
|
|
6930
|
+
fromValues = configOrFrom;
|
|
6931
|
+
toValues = to;
|
|
6932
|
+
config = {
|
|
6933
|
+
duration: options.duration || 1e3,
|
|
6934
|
+
delay: options.delay || 0,
|
|
6935
|
+
autoStart: options.autoStart || false,
|
|
6936
|
+
easing: options.ease || "ease-out"
|
|
6937
|
+
};
|
|
6938
|
+
} else {
|
|
6939
|
+
config = configOrFrom;
|
|
6940
|
+
const { type = "fade" } = config;
|
|
6941
|
+
switch (type) {
|
|
6942
|
+
case "fade":
|
|
6943
|
+
fromValues = { opacity: 0 };
|
|
6944
|
+
toValues = { opacity: 1 };
|
|
6945
|
+
break;
|
|
6946
|
+
case "slide":
|
|
6947
|
+
fromValues = { opacity: 0, translateX: 100 };
|
|
6948
|
+
toValues = { opacity: 1, translateX: 0 };
|
|
6949
|
+
break;
|
|
6950
|
+
case "scale":
|
|
6951
|
+
fromValues = { opacity: 0, scale: 0 };
|
|
6952
|
+
toValues = { opacity: 1, scale: 1 };
|
|
6953
|
+
break;
|
|
6954
|
+
case "rotate":
|
|
6955
|
+
fromValues = { opacity: 0, rotate: 180 };
|
|
6956
|
+
toValues = { opacity: 1, rotate: 0 };
|
|
6957
|
+
break;
|
|
6958
|
+
default:
|
|
6959
|
+
fromValues = { opacity: 0 };
|
|
6960
|
+
toValues = { opacity: 1 };
|
|
6961
|
+
}
|
|
6962
|
+
}
|
|
6963
|
+
const {
|
|
6964
|
+
duration = 1e3,
|
|
6965
|
+
delay = 0,
|
|
6966
|
+
autoStart = true,
|
|
6967
|
+
easing = "ease-out"
|
|
6968
|
+
} = config;
|
|
6969
|
+
const ref = useRef(null);
|
|
6970
|
+
const fromValuesRef = useRef(fromValues);
|
|
6971
|
+
const toValuesRef = useRef(toValues);
|
|
6972
|
+
const initialBgColor = useRef(fromValues.backgroundColor);
|
|
6973
|
+
const [state, setState] = useState({
|
|
6974
|
+
transform: "",
|
|
6975
|
+
opacity: fromValues.opacity ?? 1,
|
|
6976
|
+
backgroundColor: fromValues.backgroundColor,
|
|
6977
|
+
isAnimating: false
|
|
6978
|
+
});
|
|
6979
|
+
const easingFunction = useCallback(
|
|
6980
|
+
(t) => {
|
|
6981
|
+
switch (easing) {
|
|
6982
|
+
case "ease-in":
|
|
6983
|
+
return t * t;
|
|
6984
|
+
case "ease-out":
|
|
6985
|
+
return 1 - (1 - t) * (1 - t);
|
|
6986
|
+
case "ease-in-out":
|
|
6987
|
+
return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
|
|
6988
|
+
default:
|
|
6989
|
+
return t;
|
|
6990
|
+
}
|
|
6991
|
+
},
|
|
6992
|
+
[easing]
|
|
6993
|
+
);
|
|
6994
|
+
const interpolate = useCallback(
|
|
6995
|
+
(from, to2, progress) => {
|
|
6996
|
+
return from + (to2 - from) * progress;
|
|
6997
|
+
},
|
|
6998
|
+
[]
|
|
6999
|
+
);
|
|
7000
|
+
const updateMotion = useCallback(
|
|
7001
|
+
(progress) => {
|
|
7002
|
+
const easedProgress = easingFunction(progress);
|
|
7003
|
+
const from = fromValuesRef.current;
|
|
7004
|
+
const to2 = toValuesRef.current;
|
|
7005
|
+
const newState = {
|
|
7006
|
+
transform: "",
|
|
7007
|
+
opacity: 1,
|
|
7008
|
+
backgroundColor: initialBgColor.current,
|
|
7009
|
+
isAnimating: true
|
|
7010
|
+
};
|
|
7011
|
+
if (from.opacity !== void 0 && to2.opacity !== void 0) {
|
|
7012
|
+
newState.opacity = interpolate(from.opacity, to2.opacity, easedProgress);
|
|
7013
|
+
}
|
|
7014
|
+
const transforms = [];
|
|
7015
|
+
if (from.translateX !== void 0 && to2.translateX !== void 0) {
|
|
7016
|
+
const translateX = interpolate(
|
|
7017
|
+
from.translateX,
|
|
7018
|
+
to2.translateX,
|
|
7019
|
+
easedProgress
|
|
7020
|
+
);
|
|
7021
|
+
transforms.push(`translateX(${translateX}px)`);
|
|
7022
|
+
}
|
|
7023
|
+
if (from.translateY !== void 0 && to2.translateY !== void 0) {
|
|
7024
|
+
const translateY = interpolate(
|
|
7025
|
+
from.translateY,
|
|
7026
|
+
to2.translateY,
|
|
7027
|
+
easedProgress
|
|
7028
|
+
);
|
|
7029
|
+
transforms.push(`translateY(${translateY}px)`);
|
|
7030
|
+
}
|
|
7031
|
+
if (from.scale !== void 0 && to2.scale !== void 0) {
|
|
7032
|
+
const scaleVal = interpolate(from.scale, to2.scale, easedProgress);
|
|
7033
|
+
transforms.push(`scale(${scaleVal})`);
|
|
7034
|
+
}
|
|
7035
|
+
if (from.rotate !== void 0 && to2.rotate !== void 0) {
|
|
7036
|
+
const rotate = interpolate(from.rotate, to2.rotate, easedProgress);
|
|
7037
|
+
transforms.push(`rotate(${rotate}deg)`);
|
|
7038
|
+
}
|
|
7039
|
+
if (transforms.length > 0) {
|
|
7040
|
+
newState.transform = transforms.join(" ");
|
|
7041
|
+
}
|
|
7042
|
+
if (from.backgroundColor && to2.backgroundColor) {
|
|
7043
|
+
newState.backgroundColor = easedProgress > 0.5 ? to2.backgroundColor : from.backgroundColor;
|
|
7044
|
+
}
|
|
7045
|
+
setState(newState);
|
|
7046
|
+
},
|
|
7047
|
+
[easingFunction, interpolate]
|
|
7048
|
+
);
|
|
7049
|
+
const start = useCallback(() => {
|
|
7050
|
+
setState((prev) => ({ ...prev, isAnimating: true }));
|
|
7051
|
+
const startTime = Date.now();
|
|
7052
|
+
const animate = () => {
|
|
7053
|
+
const elapsed = Date.now() - startTime;
|
|
7054
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
7055
|
+
updateMotion(progress);
|
|
7056
|
+
if (progress < 1) {
|
|
7057
|
+
requestAnimationFrame(animate);
|
|
7058
|
+
} else {
|
|
7059
|
+
setState((prev) => ({ ...prev, isAnimating: false }));
|
|
7060
|
+
}
|
|
7061
|
+
};
|
|
7062
|
+
setTimeout(() => {
|
|
7063
|
+
requestAnimationFrame(animate);
|
|
7064
|
+
}, delay);
|
|
7065
|
+
}, [duration, delay, updateMotion]);
|
|
7066
|
+
const reset = useCallback(() => {
|
|
7067
|
+
const from = fromValuesRef.current;
|
|
7068
|
+
setState({
|
|
7069
|
+
transform: "",
|
|
7070
|
+
opacity: from.opacity ?? 1,
|
|
7071
|
+
backgroundColor: from.backgroundColor,
|
|
7072
|
+
isAnimating: false
|
|
7073
|
+
});
|
|
7074
|
+
}, []);
|
|
7075
|
+
const stop = useCallback(() => {
|
|
7076
|
+
setState((prev) => ({ ...prev, isAnimating: false }));
|
|
7077
|
+
}, []);
|
|
7078
|
+
useEffect(() => {
|
|
7079
|
+
if (autoStart) {
|
|
7080
|
+
start();
|
|
7081
|
+
}
|
|
7082
|
+
}, []);
|
|
7083
|
+
const setRef = (element) => {
|
|
7084
|
+
if (ref.current !== element) {
|
|
7085
|
+
ref.current = element;
|
|
7086
|
+
}
|
|
7087
|
+
};
|
|
7088
|
+
const style = {
|
|
7089
|
+
opacity: state.opacity,
|
|
7090
|
+
...state.transform && { transform: state.transform },
|
|
7091
|
+
...state.backgroundColor && { backgroundColor: state.backgroundColor }
|
|
7092
|
+
};
|
|
7093
|
+
return {
|
|
7094
|
+
ref: setRef,
|
|
7095
|
+
style,
|
|
7096
|
+
transform: state.transform,
|
|
7097
|
+
opacity: state.opacity,
|
|
7098
|
+
backgroundColor: state.backgroundColor,
|
|
7099
|
+
isAnimating: state.isAnimating,
|
|
7100
|
+
start,
|
|
7101
|
+
stop,
|
|
7102
|
+
reset
|
|
7103
|
+
};
|
|
7104
|
+
}
|
|
7105
|
+
function useViewportToggle(options = {}) {
|
|
7106
|
+
const {
|
|
7107
|
+
threshold = 0.1,
|
|
7108
|
+
rootMargin = "0px",
|
|
7109
|
+
trigger = "both",
|
|
7110
|
+
once = false,
|
|
7111
|
+
showOnMount = false
|
|
7112
|
+
} = options;
|
|
7113
|
+
const [isVisible, setIsVisible] = useState(showOnMount);
|
|
7114
|
+
const [mounted, setMounted] = useState(false);
|
|
7115
|
+
const elementRef = useRef(null);
|
|
7116
|
+
const hasTriggeredRef = useRef(false);
|
|
7117
|
+
const isVisibleRef = useRef(showOnMount);
|
|
7118
|
+
isVisibleRef.current = isVisible;
|
|
7119
|
+
useEffect(() => {
|
|
7120
|
+
setMounted(true);
|
|
7121
|
+
}, []);
|
|
7122
|
+
useEffect(() => {
|
|
7123
|
+
if (!mounted || !elementRef.current) return;
|
|
7124
|
+
const observer = new IntersectionObserver(
|
|
7125
|
+
(entries) => {
|
|
7126
|
+
entries.forEach((entry) => {
|
|
7127
|
+
const isIntersecting = entry.isIntersecting;
|
|
7128
|
+
if (once && hasTriggeredRef.current) return;
|
|
7129
|
+
let shouldBeVisible = isVisibleRef.current;
|
|
7130
|
+
if (trigger === "enter" && isIntersecting) {
|
|
7131
|
+
shouldBeVisible = true;
|
|
7132
|
+
if (once) hasTriggeredRef.current = true;
|
|
7133
|
+
} else if (trigger === "exit" && !isIntersecting) {
|
|
7134
|
+
shouldBeVisible = true;
|
|
7135
|
+
if (once) hasTriggeredRef.current = true;
|
|
7136
|
+
} else if (trigger === "both") {
|
|
7137
|
+
shouldBeVisible = isIntersecting;
|
|
7138
|
+
if (once && isIntersecting) hasTriggeredRef.current = true;
|
|
7139
|
+
}
|
|
7140
|
+
setIsVisible(shouldBeVisible);
|
|
7141
|
+
});
|
|
7142
|
+
},
|
|
7143
|
+
{
|
|
7144
|
+
threshold,
|
|
7145
|
+
rootMargin
|
|
7146
|
+
}
|
|
7147
|
+
);
|
|
7148
|
+
observer.observe(elementRef.current);
|
|
7149
|
+
return () => {
|
|
7150
|
+
observer.disconnect();
|
|
7151
|
+
};
|
|
7152
|
+
}, [threshold, rootMargin, trigger, once, mounted]);
|
|
7153
|
+
return {
|
|
7154
|
+
ref: elementRef,
|
|
7155
|
+
isVisible,
|
|
7156
|
+
mounted
|
|
7157
|
+
};
|
|
7158
|
+
}
|
|
7159
|
+
function useScrollPositionToggle(options = {}) {
|
|
7160
|
+
const { threshold = 400, showOnMount = false, smooth = true } = options;
|
|
7161
|
+
const [isVisible, setIsVisible] = useState(showOnMount);
|
|
7162
|
+
const [mounted, setMounted] = useState(false);
|
|
7163
|
+
useEffect(() => {
|
|
7164
|
+
setMounted(true);
|
|
7165
|
+
}, []);
|
|
7166
|
+
useEffect(() => {
|
|
7167
|
+
if (!mounted) return;
|
|
7168
|
+
const toggleVisibility = () => {
|
|
7169
|
+
if (typeof window !== "undefined") {
|
|
7170
|
+
if (window.pageYOffset > threshold) {
|
|
7171
|
+
setIsVisible(true);
|
|
7172
|
+
} else {
|
|
7173
|
+
setIsVisible(false);
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
};
|
|
7177
|
+
toggleVisibility();
|
|
7178
|
+
window.addEventListener("scroll", toggleVisibility, { passive: true });
|
|
7179
|
+
window.addEventListener("resize", toggleVisibility, { passive: true });
|
|
7180
|
+
return () => {
|
|
7181
|
+
window.removeEventListener("scroll", toggleVisibility);
|
|
7182
|
+
window.removeEventListener("resize", toggleVisibility);
|
|
7183
|
+
};
|
|
7184
|
+
}, [threshold, mounted]);
|
|
7185
|
+
const scrollToTop = () => {
|
|
7186
|
+
if (typeof window !== "undefined") {
|
|
7187
|
+
if (smooth) {
|
|
7188
|
+
window.scrollTo({
|
|
7189
|
+
top: 0,
|
|
7190
|
+
behavior: "smooth"
|
|
7191
|
+
});
|
|
7192
|
+
} else {
|
|
7193
|
+
window.scrollTo(0, 0);
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
};
|
|
7197
|
+
return {
|
|
7198
|
+
isVisible,
|
|
7199
|
+
scrollToTop,
|
|
7200
|
+
mounted
|
|
7201
|
+
};
|
|
7202
|
+
}
|
|
7203
|
+
function Motion({
|
|
7204
|
+
as: Component = "div",
|
|
7205
|
+
type,
|
|
7206
|
+
effects,
|
|
7207
|
+
scroll,
|
|
7208
|
+
delay,
|
|
7209
|
+
duration,
|
|
7210
|
+
children,
|
|
7211
|
+
className,
|
|
7212
|
+
style: userStyle,
|
|
5681
7213
|
...rest
|
|
5682
7214
|
}) {
|
|
5683
7215
|
const scrollOptions = useMemo(() => {
|
|
@@ -5715,6 +7247,87 @@ function Motion({
|
|
|
5715
7247
|
}
|
|
5716
7248
|
);
|
|
5717
7249
|
}
|
|
7250
|
+
function useCountUp(options) {
|
|
7251
|
+
const {
|
|
7252
|
+
end,
|
|
7253
|
+
suffix = "",
|
|
7254
|
+
duration = 1500,
|
|
7255
|
+
delay = 0,
|
|
7256
|
+
active = true
|
|
7257
|
+
} = options;
|
|
7258
|
+
const [value, setValue] = useState(0);
|
|
7259
|
+
const startedRef = useRef(false);
|
|
7260
|
+
useEffect(() => {
|
|
7261
|
+
if (!active || startedRef.current) return;
|
|
7262
|
+
const timer = setTimeout(() => {
|
|
7263
|
+
startedRef.current = true;
|
|
7264
|
+
const startTime = performance.now();
|
|
7265
|
+
const animate = (now) => {
|
|
7266
|
+
const elapsed = now - startTime;
|
|
7267
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
7268
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
7269
|
+
setValue(Math.round(eased * end));
|
|
7270
|
+
if (progress < 1) requestAnimationFrame(animate);
|
|
7271
|
+
};
|
|
7272
|
+
requestAnimationFrame(animate);
|
|
7273
|
+
}, delay);
|
|
7274
|
+
return () => clearTimeout(timer);
|
|
7275
|
+
}, [active, end, duration, delay]);
|
|
7276
|
+
return { value, display: `${value}${suffix}` };
|
|
7277
|
+
}
|
|
7278
|
+
var SPRING_EASING = "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
7279
|
+
function useClipReveal(options = {}) {
|
|
7280
|
+
const {
|
|
7281
|
+
delay = 0,
|
|
7282
|
+
duration = 900,
|
|
7283
|
+
easing = SPRING_EASING,
|
|
7284
|
+
active = true
|
|
7285
|
+
} = options;
|
|
7286
|
+
const [triggered, setTriggered] = useState(false);
|
|
7287
|
+
useEffect(() => {
|
|
7288
|
+
if (active && !triggered) {
|
|
7289
|
+
setTriggered(true);
|
|
7290
|
+
}
|
|
7291
|
+
}, [active, triggered]);
|
|
7292
|
+
const containerStyle = useMemo(() => ({
|
|
7293
|
+
overflow: "hidden",
|
|
7294
|
+
display: "inline-block"
|
|
7295
|
+
}), []);
|
|
7296
|
+
const textStyle = useMemo(() => ({
|
|
7297
|
+
display: "block",
|
|
7298
|
+
transform: triggered ? "translateY(0)" : "translateY(110%)",
|
|
7299
|
+
transition: `transform ${duration}ms ${easing} ${delay}ms`
|
|
7300
|
+
}), [triggered, duration, easing, delay]);
|
|
7301
|
+
return { containerStyle, textStyle, isVisible: triggered };
|
|
7302
|
+
}
|
|
7303
|
+
var SMOOTH_EASING = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
7304
|
+
function useBlurIn(options = {}) {
|
|
7305
|
+
const {
|
|
7306
|
+
delay = 0,
|
|
7307
|
+
duration = 1200,
|
|
7308
|
+
blurAmount = 12,
|
|
7309
|
+
scale = 0.95,
|
|
7310
|
+
easing = SMOOTH_EASING,
|
|
7311
|
+
active = true
|
|
7312
|
+
} = options;
|
|
7313
|
+
const [triggered, setTriggered] = useState(false);
|
|
7314
|
+
useEffect(() => {
|
|
7315
|
+
if (active && !triggered) {
|
|
7316
|
+
setTriggered(true);
|
|
7317
|
+
}
|
|
7318
|
+
}, [active, triggered]);
|
|
7319
|
+
const style = useMemo(() => ({
|
|
7320
|
+
opacity: triggered ? 1 : 0,
|
|
7321
|
+
filter: triggered ? "blur(0px)" : `blur(${blurAmount}px)`,
|
|
7322
|
+
transform: triggered ? "scale(1)" : `scale(${scale})`,
|
|
7323
|
+
transition: [
|
|
7324
|
+
`opacity ${duration}ms ${easing} ${delay}ms`,
|
|
7325
|
+
`filter ${duration}ms ${easing} ${delay}ms`,
|
|
7326
|
+
`transform ${duration}ms ${easing} ${delay}ms`
|
|
7327
|
+
].join(", ")
|
|
7328
|
+
}), [triggered, duration, blurAmount, scale, easing, delay]);
|
|
7329
|
+
return { style, isVisible: triggered };
|
|
7330
|
+
}
|
|
5718
7331
|
function getInitialTransform(motionType, slideDistance, scaleFrom) {
|
|
5719
7332
|
switch (motionType) {
|
|
5720
7333
|
case "slideUp":
|
|
@@ -5741,7 +7354,7 @@ function useStagger(options) {
|
|
|
5741
7354
|
duration = profile.base.duration,
|
|
5742
7355
|
motionType = "fadeIn",
|
|
5743
7356
|
threshold = profile.base.threshold,
|
|
5744
|
-
easing
|
|
7357
|
+
easing = profile.base.easing
|
|
5745
7358
|
} = options;
|
|
5746
7359
|
const containerRef = useRef(null);
|
|
5747
7360
|
const [isVisible, setIsVisible] = useState(false);
|
|
@@ -5767,16 +7380,16 @@ function useStagger(options) {
|
|
|
5767
7380
|
return {
|
|
5768
7381
|
opacity: 0,
|
|
5769
7382
|
transform: initialTransform,
|
|
5770
|
-
transition: `opacity ${duration}ms ${
|
|
7383
|
+
transition: `opacity ${duration}ms ${easing} ${itemDelay}ms, transform ${duration}ms ${easing} ${itemDelay}ms`
|
|
5771
7384
|
};
|
|
5772
7385
|
}
|
|
5773
7386
|
return {
|
|
5774
7387
|
opacity: 1,
|
|
5775
7388
|
transform: "none",
|
|
5776
|
-
transition: `opacity ${duration}ms ${
|
|
7389
|
+
transition: `opacity ${duration}ms ${easing} ${itemDelay}ms, transform ${duration}ms ${easing} ${itemDelay}ms`
|
|
5777
7390
|
};
|
|
5778
7391
|
});
|
|
5779
|
-
}, [count, isVisible, staggerDelay, baseDelay, duration, motionType,
|
|
7392
|
+
}, [count, isVisible, staggerDelay, baseDelay, duration, motionType, easing, initialTransform]);
|
|
5780
7393
|
return {
|
|
5781
7394
|
containerRef,
|
|
5782
7395
|
styles,
|
|
@@ -5784,6 +7397,6 @@ function useStagger(options) {
|
|
|
5784
7397
|
};
|
|
5785
7398
|
}
|
|
5786
7399
|
|
|
5787
|
-
export {
|
|
7400
|
+
export { Motion, createLayoutTransition, observeElement, useAutoFade, useAutoPlay, useAutoScale, useAutoSlide, useBlurIn, useBounceIn, useButtonEffect, useCardList, useClickToggle, useClipReveal, useCountUp, useCustomCursor, useElementProgress, useFadeIn, useFocusToggle, useGameLoop, useGesture, useGestureMotion, useGradient, useHoverMotion, useInView, useInteractive, useKeyboardToggle, useLanguageAwareMotion, useLayoutMotion, useLoadingSpinner, useMagneticCursor, useMotion, useMotionOrchestra, useMotionState, useMouse, useNavigation, useOrchestration, usePageMotions, usePerformanceMonitor, usePulse, useReducedMotion, useReducedMotionObject, useRepeat, useScaleIn, useScrollDirection, useScrollPositionToggle, useScrollProgress, useScrollReveal, useScrollToggle, useSequence, useSimplePageMotion, useSkeleton, useSlideDown, useSlideLeft, useSlideRight, useSlideUp, useSmartMotion, useSmoothScroll, useSpringMotion, useStagger, useStickyToggle, useToggleMotion, useTypewriter, useUnifiedMotion, useViewportToggle, useVisibilityToggle, useWindowSize };
|
|
5788
7401
|
//# sourceMappingURL=index.mjs.map
|
|
5789
7402
|
//# sourceMappingURL=index.mjs.map
|