@hua-labs/motion-core 2.2.3 → 2.3.0
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/dist/index.d.mts +387 -126
- package/dist/index.mjs +2057 -963
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/dist/index.d.ts +0 -1366
- package/dist/index.js +0 -4756
- package/dist/index.js.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
2
|
+
import { createContext, useState, useRef, useCallback, useEffect, useMemo, createElement, useContext } from 'react';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/core/MotionEngine.ts
|
|
@@ -195,17 +195,11 @@ var TransitionEffects = class _TransitionEffects {
|
|
|
195
195
|
return _TransitionEffects.instance;
|
|
196
196
|
}
|
|
197
197
|
/**
|
|
198
|
-
*
|
|
198
|
+
* 공통 전환 실행 헬퍼
|
|
199
199
|
*/
|
|
200
|
-
async
|
|
200
|
+
async executeTransition(element, options, config) {
|
|
201
201
|
const transitionId = this.generateTransitionId();
|
|
202
|
-
|
|
203
|
-
const targetOpacity = options.direction === "reverse" ? 0 : 1;
|
|
204
|
-
if (options.direction === "reverse") {
|
|
205
|
-
element.style.opacity = initialOpacity.toString();
|
|
206
|
-
} else {
|
|
207
|
-
element.style.opacity = "0";
|
|
208
|
-
}
|
|
202
|
+
config.setup();
|
|
209
203
|
this.enableGPUAcceleration(element);
|
|
210
204
|
let resolveTransition;
|
|
211
205
|
const completed = new Promise((resolve) => {
|
|
@@ -214,19 +208,17 @@ var TransitionEffects = class _TransitionEffects {
|
|
|
214
208
|
const motionId = await motionEngine.motion(
|
|
215
209
|
element,
|
|
216
210
|
[
|
|
217
|
-
{ progress: 0, properties:
|
|
218
|
-
{ progress: 1, properties:
|
|
211
|
+
{ progress: 0, properties: config.keyframes[0] },
|
|
212
|
+
{ progress: 1, properties: config.keyframes[1] }
|
|
219
213
|
],
|
|
220
214
|
{
|
|
221
215
|
duration: options.duration,
|
|
222
216
|
easing: options.easing || this.getDefaultEasing(),
|
|
223
217
|
delay: options.delay,
|
|
224
218
|
onStart: options.onTransitionStart,
|
|
225
|
-
onUpdate:
|
|
226
|
-
const currentOpacity = options.direction === "reverse" ? initialOpacity * (1 - progress) : targetOpacity * progress;
|
|
227
|
-
element.style.opacity = currentOpacity.toString();
|
|
228
|
-
},
|
|
219
|
+
onUpdate: config.onUpdate,
|
|
229
220
|
onComplete: () => {
|
|
221
|
+
config.onCleanup();
|
|
230
222
|
options.onTransitionComplete?.();
|
|
231
223
|
this.activeTransitions.delete(transitionId);
|
|
232
224
|
resolveTransition();
|
|
@@ -236,224 +228,170 @@ var TransitionEffects = class _TransitionEffects {
|
|
|
236
228
|
this.activeTransitions.set(transitionId, motionId);
|
|
237
229
|
return completed;
|
|
238
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
|
+
}
|
|
239
257
|
/**
|
|
240
258
|
* 슬라이드 전환
|
|
241
259
|
*/
|
|
242
260
|
async slide(element, options) {
|
|
243
|
-
const transitionId = this.generateTransitionId();
|
|
244
261
|
const distance = options.distance || 100;
|
|
245
262
|
const initialTransform = getComputedStyle(element).transform;
|
|
246
263
|
const isReverse = options.direction === "reverse";
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
let resolveTransition;
|
|
252
|
-
const completed = new Promise((resolve) => {
|
|
253
|
-
resolveTransition = resolve;
|
|
254
|
-
});
|
|
255
|
-
const motionId = await motionEngine.motion(
|
|
256
|
-
element,
|
|
257
|
-
[
|
|
258
|
-
{ progress: 0, properties: { translateX: isReverse ? 0 : distance } },
|
|
259
|
-
{ progress: 1, properties: { translateX: isReverse ? distance : 0 } }
|
|
260
|
-
],
|
|
261
|
-
{
|
|
262
|
-
duration: options.duration,
|
|
263
|
-
easing: options.easing || this.getDefaultEasing(),
|
|
264
|
-
delay: options.delay,
|
|
265
|
-
onStart: options.onTransitionStart,
|
|
266
|
-
onUpdate: (progress) => {
|
|
267
|
-
const currentTranslateX = isReverse ? distance * progress : distance * (1 - progress);
|
|
268
|
-
element.style.transform = `translateX(${currentTranslateX}px)`;
|
|
269
|
-
},
|
|
270
|
-
onComplete: () => {
|
|
271
|
-
element.style.transform = initialTransform;
|
|
272
|
-
options.onTransitionComplete?.();
|
|
273
|
-
this.activeTransitions.delete(transitionId);
|
|
274
|
-
resolveTransition();
|
|
264
|
+
return this.executeTransition(element, options, {
|
|
265
|
+
setup: () => {
|
|
266
|
+
if (!isReverse) {
|
|
267
|
+
element.style.transform = `translateX(${distance}px)`;
|
|
275
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;
|
|
276
280
|
}
|
|
277
|
-
);
|
|
278
|
-
this.activeTransitions.set(transitionId, motionId);
|
|
279
|
-
return completed;
|
|
281
|
+
});
|
|
280
282
|
}
|
|
281
283
|
/**
|
|
282
284
|
* 스케일 전환
|
|
283
285
|
*/
|
|
284
286
|
async scale(element, options) {
|
|
285
|
-
const transitionId = this.generateTransitionId();
|
|
286
287
|
const scaleValue = options.scale || 0.8;
|
|
287
288
|
const initialTransform = getComputedStyle(element).transform;
|
|
288
289
|
const isReverse = options.direction === "reverse";
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
let resolveTransition;
|
|
294
|
-
const completed = new Promise((resolve) => {
|
|
295
|
-
resolveTransition = resolve;
|
|
296
|
-
});
|
|
297
|
-
const motionId = await motionEngine.motion(
|
|
298
|
-
element,
|
|
299
|
-
[
|
|
300
|
-
{ progress: 0, properties: { scale: isReverse ? 1 : scaleValue } },
|
|
301
|
-
{ progress: 1, properties: { scale: isReverse ? scaleValue : 1 } }
|
|
302
|
-
],
|
|
303
|
-
{
|
|
304
|
-
duration: options.duration,
|
|
305
|
-
easing: options.easing || this.getDefaultEasing(),
|
|
306
|
-
delay: options.delay,
|
|
307
|
-
onStart: options.onTransitionStart,
|
|
308
|
-
onUpdate: (progress) => {
|
|
309
|
-
const currentScale = isReverse ? 1 - (1 - scaleValue) * progress : scaleValue + (1 - scaleValue) * progress;
|
|
310
|
-
element.style.transform = `scale(${currentScale})`;
|
|
311
|
-
},
|
|
312
|
-
onComplete: () => {
|
|
313
|
-
element.style.transform = initialTransform;
|
|
314
|
-
options.onTransitionComplete?.();
|
|
315
|
-
this.activeTransitions.delete(transitionId);
|
|
316
|
-
resolveTransition();
|
|
290
|
+
return this.executeTransition(element, options, {
|
|
291
|
+
setup: () => {
|
|
292
|
+
if (!isReverse) {
|
|
293
|
+
element.style.transform = `scale(${scaleValue})`;
|
|
317
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;
|
|
318
306
|
}
|
|
319
|
-
);
|
|
320
|
-
this.activeTransitions.set(transitionId, motionId);
|
|
321
|
-
return completed;
|
|
307
|
+
});
|
|
322
308
|
}
|
|
323
309
|
/**
|
|
324
310
|
* 플립 전환 (3D 회전)
|
|
325
311
|
*/
|
|
326
312
|
async flip(element, options) {
|
|
327
|
-
const transitionId = this.generateTransitionId();
|
|
328
313
|
const perspective = options.perspective || 1e3;
|
|
329
314
|
const initialTransform = getComputedStyle(element).transform;
|
|
330
315
|
const isReverse = options.direction === "reverse";
|
|
331
|
-
element
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
let resolveTransition;
|
|
338
|
-
const completed = new Promise((resolve) => {
|
|
339
|
-
resolveTransition = resolve;
|
|
340
|
-
});
|
|
341
|
-
const motionId = await motionEngine.motion(
|
|
342
|
-
element,
|
|
343
|
-
[
|
|
344
|
-
{ progress: 0, properties: { rotateY: isReverse ? 0 : 90 } },
|
|
345
|
-
{ progress: 1, properties: { rotateY: isReverse ? 90 : 0 } }
|
|
346
|
-
],
|
|
347
|
-
{
|
|
348
|
-
duration: options.duration,
|
|
349
|
-
easing: options.easing || this.getDefaultEasing(),
|
|
350
|
-
delay: options.delay,
|
|
351
|
-
onStart: options.onTransitionStart,
|
|
352
|
-
onUpdate: (progress) => {
|
|
353
|
-
const currentRotateY = isReverse ? 90 * progress : 90 * (1 - progress);
|
|
354
|
-
element.style.transform = `rotateY(${currentRotateY}deg)`;
|
|
355
|
-
},
|
|
356
|
-
onComplete: () => {
|
|
357
|
-
element.style.transform = initialTransform;
|
|
358
|
-
element.style.perspective = "";
|
|
359
|
-
element.style.transformStyle = "";
|
|
360
|
-
options.onTransitionComplete?.();
|
|
361
|
-
this.activeTransitions.delete(transitionId);
|
|
362
|
-
resolveTransition();
|
|
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)`;
|
|
363
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 = "";
|
|
364
336
|
}
|
|
365
|
-
);
|
|
366
|
-
this.activeTransitions.set(transitionId, motionId);
|
|
367
|
-
return completed;
|
|
337
|
+
});
|
|
368
338
|
}
|
|
369
339
|
/**
|
|
370
340
|
* 큐브 전환 (3D 큐브 회전)
|
|
371
341
|
*/
|
|
372
342
|
async cube(element, options) {
|
|
373
|
-
const transitionId = this.generateTransitionId();
|
|
374
343
|
const perspective = options.perspective || 1200;
|
|
375
344
|
const initialTransform = getComputedStyle(element).transform;
|
|
376
345
|
const isReverse = options.direction === "reverse";
|
|
377
|
-
element
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
let resolveTransition;
|
|
384
|
-
const completed = new Promise((resolve) => {
|
|
385
|
-
resolveTransition = resolve;
|
|
386
|
-
});
|
|
387
|
-
const motionId = await motionEngine.motion(
|
|
388
|
-
element,
|
|
389
|
-
[
|
|
390
|
-
{ progress: 0, properties: { rotateX: isReverse ? 0 : 90, rotateY: isReverse ? 0 : 45 } },
|
|
391
|
-
{ progress: 1, properties: { rotateX: isReverse ? 90 : 0, rotateY: isReverse ? 45 : 0 } }
|
|
392
|
-
],
|
|
393
|
-
{
|
|
394
|
-
duration: options.duration,
|
|
395
|
-
easing: options.easing || this.getDefaultEasing(),
|
|
396
|
-
delay: options.delay,
|
|
397
|
-
onStart: options.onTransitionStart,
|
|
398
|
-
onUpdate: (progress) => {
|
|
399
|
-
const currentRotateX = isReverse ? 90 * progress : 90 * (1 - progress);
|
|
400
|
-
const currentRotateY = isReverse ? 45 * progress : 45 * (1 - progress);
|
|
401
|
-
element.style.transform = `rotateX(${currentRotateX}deg) rotateY(${currentRotateY}deg)`;
|
|
402
|
-
},
|
|
403
|
-
onComplete: () => {
|
|
404
|
-
element.style.transform = initialTransform;
|
|
405
|
-
element.style.perspective = "";
|
|
406
|
-
element.style.transformStyle = "";
|
|
407
|
-
options.onTransitionComplete?.();
|
|
408
|
-
this.activeTransitions.delete(transitionId);
|
|
409
|
-
resolveTransition();
|
|
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)`;
|
|
410
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 = "";
|
|
411
367
|
}
|
|
412
|
-
);
|
|
413
|
-
this.activeTransitions.set(transitionId, motionId);
|
|
414
|
-
return completed;
|
|
368
|
+
});
|
|
415
369
|
}
|
|
416
370
|
/**
|
|
417
371
|
* 모프 전환 (복합 변형)
|
|
418
372
|
*/
|
|
419
373
|
async morph(element, options) {
|
|
420
|
-
const transitionId = this.generateTransitionId();
|
|
421
374
|
const initialTransform = getComputedStyle(element).transform;
|
|
422
375
|
const isReverse = options.direction === "reverse";
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
let resolveTransition;
|
|
428
|
-
const completed = new Promise((resolve) => {
|
|
429
|
-
resolveTransition = resolve;
|
|
430
|
-
});
|
|
431
|
-
const motionId = await motionEngine.motion(
|
|
432
|
-
element,
|
|
433
|
-
[
|
|
434
|
-
{ progress: 0, properties: { scale: isReverse ? 1 : 0.9, rotate: isReverse ? 0 : 5 } },
|
|
435
|
-
{ progress: 1, properties: { scale: isReverse ? 0.9 : 1, rotate: isReverse ? 5 : 0 } }
|
|
436
|
-
],
|
|
437
|
-
{
|
|
438
|
-
duration: options.duration,
|
|
439
|
-
easing: options.easing || this.getDefaultEasing(),
|
|
440
|
-
delay: options.delay,
|
|
441
|
-
onStart: options.onTransitionStart,
|
|
442
|
-
onUpdate: (progress) => {
|
|
443
|
-
const currentScale = isReverse ? 1 - 0.1 * progress : 0.9 + 0.1 * progress;
|
|
444
|
-
const currentRotate = isReverse ? 5 * progress : 5 * (1 - progress);
|
|
445
|
-
element.style.transform = `scale(${currentScale}) rotate(${currentRotate}deg)`;
|
|
446
|
-
},
|
|
447
|
-
onComplete: () => {
|
|
448
|
-
element.style.transform = initialTransform;
|
|
449
|
-
options.onTransitionComplete?.();
|
|
450
|
-
this.activeTransitions.delete(transitionId);
|
|
451
|
-
resolveTransition();
|
|
376
|
+
return this.executeTransition(element, options, {
|
|
377
|
+
setup: () => {
|
|
378
|
+
if (!isReverse) {
|
|
379
|
+
element.style.transform = `scale(0.9) rotate(5deg)`;
|
|
452
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;
|
|
453
393
|
}
|
|
454
|
-
);
|
|
455
|
-
this.activeTransitions.set(transitionId, motionId);
|
|
456
|
-
return completed;
|
|
394
|
+
});
|
|
457
395
|
}
|
|
458
396
|
/**
|
|
459
397
|
* 전환 중지
|
|
@@ -515,390 +453,91 @@ var TransitionEffects = class _TransitionEffects {
|
|
|
515
453
|
};
|
|
516
454
|
var transitionEffects = TransitionEffects.getInstance();
|
|
517
455
|
|
|
518
|
-
// src/
|
|
519
|
-
var
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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
|
|
539
499
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (deltaTime > 0) {
|
|
582
|
-
this.metrics.fps = Math.round(1e3 / deltaTime);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* GPU 가속 활성화
|
|
587
|
-
*/
|
|
588
|
-
enableGPUAcceleration(element) {
|
|
589
|
-
if (!this.config.enableGPUAcceleration) return;
|
|
590
|
-
try {
|
|
591
|
-
element.style.willChange = "transform, opacity";
|
|
592
|
-
element.style.transform = "translateZ(0)";
|
|
593
|
-
element.style.backfaceVisibility = "hidden";
|
|
594
|
-
element.style.transformStyle = "preserve-3d";
|
|
595
|
-
this.registerLayer(element);
|
|
596
|
-
} catch (error) {
|
|
597
|
-
if (process.env.NODE_ENV === "development") {
|
|
598
|
-
console.warn("GPU acceleration failed:", error);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* 레이어 분리 및 최적화
|
|
604
|
-
*/
|
|
605
|
-
createOptimizedLayer(element) {
|
|
606
|
-
if (!this.config.enableLayerSeparation) return;
|
|
607
|
-
try {
|
|
608
|
-
element.style.transform = "translateZ(0)";
|
|
609
|
-
element.style.backfaceVisibility = "hidden";
|
|
610
|
-
element.style.perspective = "1000px";
|
|
611
|
-
this.registerLayer(element);
|
|
612
|
-
this.checkLayerLimit();
|
|
613
|
-
} catch (error) {
|
|
614
|
-
if (process.env.NODE_ENV === "development") {
|
|
615
|
-
console.warn("Layer optimization failed:", error);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* 레이어 등록
|
|
621
|
-
*/
|
|
622
|
-
registerLayer(element) {
|
|
623
|
-
if (this.layerRegistry.has(element)) return;
|
|
624
|
-
this.layerRegistry.add(element);
|
|
625
|
-
this.metrics.layerCount = this.layerRegistry.size;
|
|
626
|
-
this.checkMemoryUsage();
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* 레이어 제거
|
|
630
|
-
*/
|
|
631
|
-
removeLayer(element) {
|
|
632
|
-
if (this.layerRegistry.has(element)) {
|
|
633
|
-
this.layerRegistry.delete(element);
|
|
634
|
-
this.metrics.layerCount = this.layerRegistry.size;
|
|
635
|
-
element.style.willChange = "auto";
|
|
636
|
-
element.style.transform = "";
|
|
637
|
-
element.style.backfaceVisibility = "";
|
|
638
|
-
element.style.transformStyle = "";
|
|
639
|
-
element.style.perspective = "";
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* 레이어 수 제한 체크
|
|
644
|
-
*/
|
|
645
|
-
checkLayerLimit() {
|
|
646
|
-
if (this.metrics.layerCount > this.config.maxLayerCount) {
|
|
647
|
-
if (process.env.NODE_ENV === "development") {
|
|
648
|
-
console.warn(`Layer count (${this.metrics.layerCount}) exceeds limit (${this.config.maxLayerCount})`);
|
|
649
|
-
}
|
|
650
|
-
this.cleanupOldLayers();
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* 오래된 레이어 정리
|
|
655
|
-
*/
|
|
656
|
-
cleanupOldLayers() {
|
|
657
|
-
const layersToRemove = Array.from(this.layerRegistry).slice(0, 10);
|
|
658
|
-
layersToRemove.forEach((layer) => {
|
|
659
|
-
this.removeLayer(layer);
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* 메모리 사용량 체크
|
|
664
|
-
*/
|
|
665
|
-
checkMemoryUsage() {
|
|
666
|
-
if (!this.config.enableMemoryOptimization) return;
|
|
667
|
-
if ("memory" in performance) {
|
|
668
|
-
const memory = performance.memory;
|
|
669
|
-
this.metrics.memoryUsage = memory.usedJSHeapSize;
|
|
670
|
-
if (memory.usedJSHeapSize > this.config.memoryThreshold) {
|
|
671
|
-
if (process.env.NODE_ENV === "development") {
|
|
672
|
-
console.warn("Memory usage high, cleaning up...");
|
|
673
|
-
}
|
|
674
|
-
this.cleanupMemory();
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* 메모리 정리
|
|
680
|
-
*/
|
|
681
|
-
cleanupMemory() {
|
|
682
|
-
if ("gc" in window) {
|
|
683
|
-
try {
|
|
684
|
-
window.gc();
|
|
685
|
-
} catch (error) {
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
this.cleanupOldLayers();
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* 성능 최적화 설정 업데이트
|
|
692
|
-
*/
|
|
693
|
-
updateConfig(newConfig) {
|
|
694
|
-
this.config = { ...this.config, ...newConfig };
|
|
695
|
-
if (!this.config.enableGPUAcceleration) {
|
|
696
|
-
this.disableAllGPUAcceleration();
|
|
697
|
-
}
|
|
698
|
-
if (!this.config.enableLayerSeparation) {
|
|
699
|
-
this.disableAllLayers();
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* 모든 GPU 가속 비활성화
|
|
704
|
-
*/
|
|
705
|
-
disableAllGPUAcceleration() {
|
|
706
|
-
this.layerRegistry.forEach((element) => {
|
|
707
|
-
element.style.willChange = "auto";
|
|
708
|
-
element.style.transform = "";
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* 모든 레이어 비활성화
|
|
713
|
-
*/
|
|
714
|
-
disableAllLayers() {
|
|
715
|
-
this.layerRegistry.forEach((element) => {
|
|
716
|
-
this.removeLayer(element);
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* 성능 메트릭 가져오기
|
|
721
|
-
*/
|
|
722
|
-
getMetrics() {
|
|
723
|
-
return { ...this.metrics };
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* 성능 모니터링 시작
|
|
727
|
-
*/
|
|
728
|
-
startMonitoring() {
|
|
729
|
-
if (this.isMonitoring) return;
|
|
730
|
-
this.isMonitoring = true;
|
|
731
|
-
this.monitoringInterval = setInterval(() => {
|
|
732
|
-
this.updateMetrics();
|
|
733
|
-
}, 1e3);
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* 성능 모니터링 중지
|
|
737
|
-
*/
|
|
738
|
-
stopMonitoring() {
|
|
739
|
-
if (!this.isMonitoring) return;
|
|
740
|
-
this.isMonitoring = false;
|
|
741
|
-
if (this.monitoringInterval) {
|
|
742
|
-
clearInterval(this.monitoringInterval);
|
|
743
|
-
this.monitoringInterval = void 0;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
/**
|
|
747
|
-
* 메트릭 업데이트
|
|
748
|
-
*/
|
|
749
|
-
updateMetrics() {
|
|
750
|
-
if ("memory" in performance) {
|
|
751
|
-
const memory = performance.memory;
|
|
752
|
-
this.metrics.memoryUsage = memory.usedJSHeapSize;
|
|
753
|
-
}
|
|
754
|
-
this.metrics.layerCount = this.layerRegistry.size;
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* 성능 리포트 생성
|
|
758
|
-
*/
|
|
759
|
-
generateReport() {
|
|
760
|
-
const metrics = this.getMetrics();
|
|
761
|
-
return `
|
|
762
|
-
=== HUA Motion Performance Report ===
|
|
763
|
-
FPS: ${metrics.fps}
|
|
764
|
-
Active Layers: ${metrics.layerCount}
|
|
765
|
-
Memory Usage: ${this.formatBytes(metrics.memoryUsage || 0)}
|
|
766
|
-
Active Motions: ${metrics.activeMotions}
|
|
767
|
-
GPU Acceleration: ${this.config.enableGPUAcceleration ? "Enabled" : "Disabled"}
|
|
768
|
-
Layer Separation: ${this.config.enableLayerSeparation ? "Enabled" : "Disabled"}
|
|
769
|
-
Memory Optimization: ${this.config.enableMemoryOptimization ? "Enabled" : "Disabled"}
|
|
770
|
-
=====================================
|
|
771
|
-
`.trim();
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* 바이트 단위 포맷팅
|
|
775
|
-
*/
|
|
776
|
-
formatBytes(bytes) {
|
|
777
|
-
if (bytes === 0) return "0 B";
|
|
778
|
-
const k = 1024;
|
|
779
|
-
const sizes = ["B", "KB", "MB", "GB"];
|
|
780
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
781
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* 성능 최적화 권장사항
|
|
785
|
-
*/
|
|
786
|
-
getOptimizationRecommendations() {
|
|
787
|
-
const recommendations = [];
|
|
788
|
-
const metrics = this.getMetrics();
|
|
789
|
-
if (metrics.fps < this.config.targetFPS) {
|
|
790
|
-
recommendations.push("FPS\uAC00 \uB0AE\uC2B5\uB2C8\uB2E4. GPU \uAC00\uC18D\uC744 \uD65C\uC131\uD654\uD558\uAC70\uB098 \uB808\uC774\uC5B4 \uC218\uB97C \uC904\uC774\uC138\uC694.");
|
|
791
|
-
}
|
|
792
|
-
if (metrics.layerCount > this.config.maxLayerCount * 0.8) {
|
|
793
|
-
recommendations.push("\uB808\uC774\uC5B4 \uC218\uAC00 \uB9CE\uC2B5\uB2C8\uB2E4. \uBD88\uD544\uC694\uD55C \uB808\uC774\uC5B4\uB97C \uC815\uB9AC\uD558\uC138\uC694.");
|
|
794
|
-
}
|
|
795
|
-
if (metrics.memoryUsage && metrics.memoryUsage > this.config.memoryThreshold * 0.8) {
|
|
796
|
-
recommendations.push("\uBA54\uBAA8\uB9AC \uC0AC\uC6A9\uB7C9\uC774 \uB192\uC2B5\uB2C8\uB2E4. \uBA54\uBAA8\uB9AC \uC815\uB9AC\uB97C \uACE0\uB824\uD558\uC138\uC694.");
|
|
797
|
-
}
|
|
798
|
-
return recommendations;
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* 정리
|
|
802
|
-
*/
|
|
803
|
-
destroy() {
|
|
804
|
-
this.stopMonitoring();
|
|
805
|
-
if (this.performanceObserver) {
|
|
806
|
-
this.performanceObserver.disconnect();
|
|
807
|
-
this.performanceObserver = null;
|
|
808
|
-
}
|
|
809
|
-
this.layerRegistry.forEach((element) => {
|
|
810
|
-
this.removeLayer(element);
|
|
811
|
-
});
|
|
812
|
-
this.layerRegistry.clear();
|
|
813
|
-
}
|
|
814
|
-
};
|
|
815
|
-
var performanceOptimizer = PerformanceOptimizer.getInstance();
|
|
816
|
-
|
|
817
|
-
// src/presets/index.ts
|
|
818
|
-
var MOTION_PRESETS = {
|
|
819
|
-
hero: {
|
|
820
|
-
entrance: "fadeIn",
|
|
821
|
-
delay: 200,
|
|
822
|
-
duration: 800,
|
|
823
|
-
hover: false,
|
|
824
|
-
click: false
|
|
825
|
-
},
|
|
826
|
-
title: {
|
|
827
|
-
entrance: "slideUp",
|
|
828
|
-
delay: 400,
|
|
829
|
-
duration: 700,
|
|
830
|
-
hover: false,
|
|
831
|
-
click: false
|
|
832
|
-
},
|
|
833
|
-
button: {
|
|
834
|
-
entrance: "scaleIn",
|
|
835
|
-
delay: 600,
|
|
836
|
-
duration: 300,
|
|
837
|
-
hover: true,
|
|
838
|
-
click: true
|
|
839
|
-
},
|
|
840
|
-
card: {
|
|
841
|
-
entrance: "slideUp",
|
|
842
|
-
delay: 800,
|
|
843
|
-
duration: 500,
|
|
844
|
-
hover: true,
|
|
845
|
-
click: false
|
|
846
|
-
},
|
|
847
|
-
text: {
|
|
848
|
-
entrance: "fadeIn",
|
|
849
|
-
delay: 200,
|
|
850
|
-
duration: 600,
|
|
851
|
-
hover: false,
|
|
852
|
-
click: false
|
|
853
|
-
},
|
|
854
|
-
image: {
|
|
855
|
-
entrance: "scaleIn",
|
|
856
|
-
delay: 400,
|
|
857
|
-
duration: 600,
|
|
858
|
-
hover: true,
|
|
859
|
-
click: false
|
|
860
|
-
}
|
|
861
|
-
};
|
|
862
|
-
var PAGE_MOTIONS = {
|
|
863
|
-
// 홈페이지
|
|
864
|
-
home: {
|
|
865
|
-
hero: { type: "hero" },
|
|
866
|
-
title: { type: "title" },
|
|
867
|
-
description: { type: "text" },
|
|
868
|
-
cta: { type: "button" },
|
|
869
|
-
feature1: { type: "card" },
|
|
870
|
-
feature2: { type: "card" },
|
|
871
|
-
feature3: { type: "card" }
|
|
872
|
-
},
|
|
873
|
-
// 대시보드
|
|
874
|
-
dashboard: {
|
|
875
|
-
header: { type: "hero" },
|
|
876
|
-
sidebar: { type: "card", entrance: "slideLeft" },
|
|
877
|
-
main: { type: "text", entrance: "fadeIn" },
|
|
878
|
-
card1: { type: "card" },
|
|
879
|
-
card2: { type: "card" },
|
|
880
|
-
card3: { type: "card" },
|
|
881
|
-
chart: { type: "image" }
|
|
882
|
-
},
|
|
883
|
-
// 제품 페이지
|
|
884
|
-
product: {
|
|
885
|
-
hero: { type: "hero" },
|
|
886
|
-
title: { type: "title" },
|
|
887
|
-
image: { type: "image" },
|
|
888
|
-
description: { type: "text" },
|
|
889
|
-
price: { type: "text" },
|
|
890
|
-
buyButton: { type: "button" },
|
|
891
|
-
features: { type: "card" }
|
|
892
|
-
},
|
|
893
|
-
// 블로그
|
|
894
|
-
blog: {
|
|
895
|
-
header: { type: "hero" },
|
|
896
|
-
title: { type: "title" },
|
|
897
|
-
content: { type: "text" },
|
|
898
|
-
sidebar: { type: "card", entrance: "slideRight" },
|
|
899
|
-
related1: { type: "card" },
|
|
900
|
-
related2: { type: "card" },
|
|
901
|
-
related3: { type: "card" }
|
|
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" }
|
|
902
541
|
}
|
|
903
542
|
};
|
|
904
543
|
function mergeWithPreset(preset, custom = {}) {
|
|
@@ -1660,39 +1299,261 @@ function useSmartMotion(options = {}) {
|
|
|
1660
1299
|
isClicked: state.isClicked
|
|
1661
1300
|
};
|
|
1662
1301
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1302
|
+
|
|
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;
|
|
1678
1405
|
}
|
|
1406
|
+
return profile;
|
|
1679
1407
|
}
|
|
1680
|
-
function
|
|
1681
|
-
return
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
if (type === "bounceIn") return "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
1686
|
-
return "ease-out";
|
|
1408
|
+
function mergeProfileOverrides(base, overrides) {
|
|
1409
|
+
return deepMerge(
|
|
1410
|
+
base,
|
|
1411
|
+
overrides
|
|
1412
|
+
);
|
|
1687
1413
|
}
|
|
1688
|
-
function
|
|
1689
|
-
const
|
|
1690
|
-
const
|
|
1691
|
-
|
|
1692
|
-
|
|
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
|
+
}
|
|
1693
1427
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
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
|
+
// src/utils/sharedIntersectionObserver.ts
|
|
1453
|
+
var pool = /* @__PURE__ */ new Map();
|
|
1454
|
+
function makeKey(threshold, rootMargin) {
|
|
1455
|
+
const th = Array.isArray(threshold) ? threshold.join(",") : String(threshold);
|
|
1456
|
+
return `${th}|${rootMargin}`;
|
|
1457
|
+
}
|
|
1458
|
+
function handleIntersections(group, entries) {
|
|
1459
|
+
for (const entry of entries) {
|
|
1460
|
+
const subs = group.elements.get(entry.target);
|
|
1461
|
+
if (!subs) continue;
|
|
1462
|
+
const snapshot = [...subs];
|
|
1463
|
+
for (const sub of snapshot) {
|
|
1464
|
+
sub.callback(entry);
|
|
1465
|
+
if (sub.once && entry.isIntersecting) {
|
|
1466
|
+
removeSub(group, entry.target, sub);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function removeSub(group, element, sub) {
|
|
1472
|
+
const subs = group.elements.get(element);
|
|
1473
|
+
if (!subs) return;
|
|
1474
|
+
const idx = subs.indexOf(sub);
|
|
1475
|
+
if (idx >= 0) subs.splice(idx, 1);
|
|
1476
|
+
if (subs.length === 0) {
|
|
1477
|
+
group.elements.delete(element);
|
|
1478
|
+
group.observer.unobserve(element);
|
|
1479
|
+
if (group.elements.size === 0) {
|
|
1480
|
+
group.observer.disconnect();
|
|
1481
|
+
for (const [key, g] of pool) {
|
|
1482
|
+
if (g === group) {
|
|
1483
|
+
pool.delete(key);
|
|
1484
|
+
break;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function observeElement(element, callback, options, once) {
|
|
1491
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
1492
|
+
return () => {
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
const threshold = options?.threshold ?? 0;
|
|
1496
|
+
const rootMargin = options?.rootMargin ?? "0px";
|
|
1497
|
+
const key = makeKey(threshold, rootMargin);
|
|
1498
|
+
let group = pool.get(key);
|
|
1499
|
+
if (!group) {
|
|
1500
|
+
const observer = new IntersectionObserver(
|
|
1501
|
+
(entries) => handleIntersections(group, entries),
|
|
1502
|
+
{ threshold, rootMargin }
|
|
1503
|
+
);
|
|
1504
|
+
group = { observer, elements: /* @__PURE__ */ new Map() };
|
|
1505
|
+
pool.set(key, group);
|
|
1506
|
+
}
|
|
1507
|
+
const sub = { callback, once: once ?? false };
|
|
1508
|
+
const subs = group.elements.get(element);
|
|
1509
|
+
if (subs) {
|
|
1510
|
+
subs.push(sub);
|
|
1511
|
+
} else {
|
|
1512
|
+
group.elements.set(element, [sub]);
|
|
1513
|
+
group.observer.observe(element);
|
|
1514
|
+
}
|
|
1515
|
+
let unsubscribed = false;
|
|
1516
|
+
return () => {
|
|
1517
|
+
if (unsubscribed) return;
|
|
1518
|
+
unsubscribed = true;
|
|
1519
|
+
removeSub(group, element, sub);
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// src/hooks/useUnifiedMotion.ts
|
|
1524
|
+
function getInitialStyle(type, distance) {
|
|
1525
|
+
switch (type) {
|
|
1526
|
+
case "slideUp":
|
|
1527
|
+
return { opacity: 0, transform: `translateY(${distance}px)` };
|
|
1528
|
+
case "slideLeft":
|
|
1529
|
+
return { opacity: 0, transform: `translateX(${distance}px)` };
|
|
1530
|
+
case "slideRight":
|
|
1531
|
+
return { opacity: 0, transform: `translateX(-${distance}px)` };
|
|
1532
|
+
case "scaleIn":
|
|
1533
|
+
return { opacity: 0, transform: "scale(0)" };
|
|
1534
|
+
case "bounceIn":
|
|
1535
|
+
return { opacity: 0, transform: "scale(0)" };
|
|
1536
|
+
case "fadeIn":
|
|
1537
|
+
default:
|
|
1538
|
+
return { opacity: 0, transform: "none" };
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
function getVisibleStyle() {
|
|
1542
|
+
return { opacity: 1, transform: "none" };
|
|
1543
|
+
}
|
|
1544
|
+
function getEasingForType(type, easing2) {
|
|
1545
|
+
if (easing2) return easing2;
|
|
1546
|
+
if (type === "bounceIn") return "cubic-bezier(0.34, 1.56, 0.64, 1)";
|
|
1547
|
+
return "ease-out";
|
|
1548
|
+
}
|
|
1549
|
+
function getMultiEffectInitialStyle(effects, defaultDistance) {
|
|
1550
|
+
const style = {};
|
|
1551
|
+
const transforms = [];
|
|
1552
|
+
if (effects.fade) {
|
|
1553
|
+
style.opacity = 0;
|
|
1554
|
+
}
|
|
1555
|
+
if (effects.slide) {
|
|
1556
|
+
const config = typeof effects.slide === "object" ? effects.slide : {};
|
|
1696
1557
|
const direction = config.direction ?? "up";
|
|
1697
1558
|
const distance = config.distance ?? defaultDistance;
|
|
1698
1559
|
switch (direction) {
|
|
@@ -1753,16 +1614,17 @@ function getMultiEffectEasing(effects, easing2) {
|
|
|
1753
1614
|
return "ease-out";
|
|
1754
1615
|
}
|
|
1755
1616
|
function useUnifiedMotion(options) {
|
|
1617
|
+
const profile = useMotionProfile();
|
|
1756
1618
|
const {
|
|
1757
1619
|
type,
|
|
1758
1620
|
effects,
|
|
1759
|
-
duration =
|
|
1621
|
+
duration = profile.base.duration,
|
|
1760
1622
|
autoStart = true,
|
|
1761
1623
|
delay = 0,
|
|
1762
1624
|
easing: easing2,
|
|
1763
|
-
threshold =
|
|
1764
|
-
triggerOnce =
|
|
1765
|
-
distance =
|
|
1625
|
+
threshold = profile.base.threshold,
|
|
1626
|
+
triggerOnce = profile.base.triggerOnce,
|
|
1627
|
+
distance = profile.entrance.slide.distance,
|
|
1766
1628
|
onComplete,
|
|
1767
1629
|
onStart,
|
|
1768
1630
|
onStop,
|
|
@@ -1774,7 +1636,6 @@ function useUnifiedMotion(options) {
|
|
|
1774
1636
|
const [isVisible, setIsVisible] = useState(false);
|
|
1775
1637
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
1776
1638
|
const [progress, setProgress] = useState(0);
|
|
1777
|
-
const observerRef = useRef(null);
|
|
1778
1639
|
const timeoutRef = useRef(null);
|
|
1779
1640
|
const startRef = useRef(() => {
|
|
1780
1641
|
});
|
|
@@ -1807,23 +1668,14 @@ function useUnifiedMotion(options) {
|
|
|
1807
1668
|
}, [stop, onReset]);
|
|
1808
1669
|
useEffect(() => {
|
|
1809
1670
|
if (!ref.current || !autoStart) return;
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
startRef.current();
|
|
1815
|
-
if (triggerOnce) {
|
|
1816
|
-
observerRef.current?.disconnect();
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
});
|
|
1671
|
+
return observeElement(
|
|
1672
|
+
ref.current,
|
|
1673
|
+
(entry) => {
|
|
1674
|
+
if (entry.isIntersecting) startRef.current();
|
|
1820
1675
|
},
|
|
1821
|
-
{ threshold }
|
|
1676
|
+
{ threshold },
|
|
1677
|
+
triggerOnce
|
|
1822
1678
|
);
|
|
1823
|
-
observerRef.current.observe(ref.current);
|
|
1824
|
-
return () => {
|
|
1825
|
-
observerRef.current?.disconnect();
|
|
1826
|
-
};
|
|
1827
1679
|
}, [autoStart, threshold, triggerOnce]);
|
|
1828
1680
|
useEffect(() => {
|
|
1829
1681
|
return () => stop();
|
|
@@ -1863,14 +1715,15 @@ function useUnifiedMotion(options) {
|
|
|
1863
1715
|
};
|
|
1864
1716
|
}
|
|
1865
1717
|
function useFadeIn(options = {}) {
|
|
1718
|
+
const profile = useMotionProfile();
|
|
1866
1719
|
const {
|
|
1867
1720
|
delay = 0,
|
|
1868
|
-
duration =
|
|
1869
|
-
threshold =
|
|
1870
|
-
triggerOnce =
|
|
1871
|
-
easing: easing2 =
|
|
1721
|
+
duration = profile.base.duration,
|
|
1722
|
+
threshold = profile.base.threshold,
|
|
1723
|
+
triggerOnce = profile.base.triggerOnce,
|
|
1724
|
+
easing: easing2 = profile.base.easing,
|
|
1872
1725
|
autoStart = true,
|
|
1873
|
-
initialOpacity =
|
|
1726
|
+
initialOpacity = profile.entrance.fade.initialOpacity,
|
|
1874
1727
|
targetOpacity = 1,
|
|
1875
1728
|
onComplete,
|
|
1876
1729
|
onStart,
|
|
@@ -1882,7 +1735,6 @@ function useFadeIn(options = {}) {
|
|
|
1882
1735
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
1883
1736
|
const [progress, setProgress] = useState(0);
|
|
1884
1737
|
const [nodeReady, setNodeReady] = useState(false);
|
|
1885
|
-
const observerRef = useRef(null);
|
|
1886
1738
|
const motionRef = useRef(null);
|
|
1887
1739
|
const timeoutRef = useRef(null);
|
|
1888
1740
|
const startRef = useRef(() => {
|
|
@@ -1941,31 +1793,15 @@ function useFadeIn(options = {}) {
|
|
|
1941
1793
|
}, [stop, onReset]);
|
|
1942
1794
|
useEffect(() => {
|
|
1943
1795
|
if (!ref.current || !autoStart) return;
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
startRef.current();
|
|
1949
|
-
if (triggerOnce) {
|
|
1950
|
-
observerRef.current?.disconnect();
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
});
|
|
1796
|
+
return observeElement(
|
|
1797
|
+
ref.current,
|
|
1798
|
+
(entry) => {
|
|
1799
|
+
if (entry.isIntersecting) startRef.current();
|
|
1954
1800
|
},
|
|
1955
|
-
{ threshold }
|
|
1801
|
+
{ threshold },
|
|
1802
|
+
triggerOnce
|
|
1956
1803
|
);
|
|
1957
|
-
observerRef.current.observe(ref.current);
|
|
1958
|
-
return () => {
|
|
1959
|
-
if (observerRef.current) {
|
|
1960
|
-
observerRef.current.disconnect();
|
|
1961
|
-
}
|
|
1962
|
-
};
|
|
1963
1804
|
}, [autoStart, threshold, triggerOnce, nodeReady]);
|
|
1964
|
-
useEffect(() => {
|
|
1965
|
-
if (!autoStart) {
|
|
1966
|
-
start();
|
|
1967
|
-
}
|
|
1968
|
-
}, [autoStart, start]);
|
|
1969
1805
|
useEffect(() => {
|
|
1970
1806
|
return () => {
|
|
1971
1807
|
stop();
|
|
@@ -1991,15 +1827,16 @@ function useFadeIn(options = {}) {
|
|
|
1991
1827
|
};
|
|
1992
1828
|
}
|
|
1993
1829
|
function useSlideUp(options = {}) {
|
|
1830
|
+
const profile = useMotionProfile();
|
|
1994
1831
|
const {
|
|
1995
1832
|
delay = 0,
|
|
1996
|
-
duration =
|
|
1997
|
-
threshold =
|
|
1998
|
-
triggerOnce =
|
|
1999
|
-
easing: easing2 =
|
|
1833
|
+
duration = profile.base.duration,
|
|
1834
|
+
threshold = profile.base.threshold,
|
|
1835
|
+
triggerOnce = profile.base.triggerOnce,
|
|
1836
|
+
easing: easing2 = profile.entrance.slide.easing,
|
|
2000
1837
|
autoStart = true,
|
|
2001
1838
|
direction = "up",
|
|
2002
|
-
distance =
|
|
1839
|
+
distance = profile.entrance.slide.distance,
|
|
2003
1840
|
onComplete,
|
|
2004
1841
|
onStart,
|
|
2005
1842
|
onStop,
|
|
@@ -2010,7 +1847,6 @@ function useSlideUp(options = {}) {
|
|
|
2010
1847
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
2011
1848
|
const [progress, setProgress] = useState(0);
|
|
2012
1849
|
const [nodeReady, setNodeReady] = useState(false);
|
|
2013
|
-
const observerRef = useRef(null);
|
|
2014
1850
|
const timeoutRef = useRef(null);
|
|
2015
1851
|
const startRef = useRef(() => {
|
|
2016
1852
|
});
|
|
@@ -2078,31 +1914,15 @@ function useSlideUp(options = {}) {
|
|
|
2078
1914
|
}, [stop, onReset]);
|
|
2079
1915
|
useEffect(() => {
|
|
2080
1916
|
if (!ref.current || !autoStart) return;
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
startRef.current();
|
|
2086
|
-
if (triggerOnce) {
|
|
2087
|
-
observerRef.current?.disconnect();
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
});
|
|
1917
|
+
return observeElement(
|
|
1918
|
+
ref.current,
|
|
1919
|
+
(entry) => {
|
|
1920
|
+
if (entry.isIntersecting) startRef.current();
|
|
2091
1921
|
},
|
|
2092
|
-
{ threshold }
|
|
1922
|
+
{ threshold },
|
|
1923
|
+
triggerOnce
|
|
2093
1924
|
);
|
|
2094
|
-
observerRef.current.observe(ref.current);
|
|
2095
|
-
return () => {
|
|
2096
|
-
if (observerRef.current) {
|
|
2097
|
-
observerRef.current.disconnect();
|
|
2098
|
-
}
|
|
2099
|
-
};
|
|
2100
1925
|
}, [autoStart, threshold, triggerOnce, nodeReady]);
|
|
2101
|
-
useEffect(() => {
|
|
2102
|
-
if (!autoStart) {
|
|
2103
|
-
start();
|
|
2104
|
-
}
|
|
2105
|
-
}, [autoStart, start]);
|
|
2106
1926
|
useEffect(() => {
|
|
2107
1927
|
return () => {
|
|
2108
1928
|
stop();
|
|
@@ -2145,15 +1965,16 @@ function useSlideRight(options = {}) {
|
|
|
2145
1965
|
return useSlideUp({ ...options, direction: "right" });
|
|
2146
1966
|
}
|
|
2147
1967
|
function useScaleIn(options = {}) {
|
|
1968
|
+
const profile = useMotionProfile();
|
|
2148
1969
|
const {
|
|
2149
1970
|
initialScale = 0,
|
|
2150
1971
|
targetScale = 1,
|
|
2151
|
-
duration =
|
|
1972
|
+
duration = profile.base.duration,
|
|
2152
1973
|
delay = 0,
|
|
2153
1974
|
autoStart = true,
|
|
2154
|
-
easing: easing2 =
|
|
2155
|
-
threshold =
|
|
2156
|
-
triggerOnce =
|
|
1975
|
+
easing: easing2 = profile.base.easing,
|
|
1976
|
+
threshold = profile.base.threshold,
|
|
1977
|
+
triggerOnce = profile.base.triggerOnce,
|
|
2157
1978
|
onComplete,
|
|
2158
1979
|
onStart,
|
|
2159
1980
|
onStop,
|
|
@@ -2165,7 +1986,6 @@ function useScaleIn(options = {}) {
|
|
|
2165
1986
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
2166
1987
|
const [isVisible, setIsVisible] = useState(autoStart ? false : true);
|
|
2167
1988
|
const [progress, setProgress] = useState(autoStart ? 0 : 1);
|
|
2168
|
-
const observerRef = useRef(null);
|
|
2169
1989
|
const timeoutRef = useRef(null);
|
|
2170
1990
|
const startRef = useRef(() => {
|
|
2171
1991
|
});
|
|
@@ -2213,25 +2033,14 @@ function useScaleIn(options = {}) {
|
|
|
2213
2033
|
}, [stop, initialScale, onReset]);
|
|
2214
2034
|
useEffect(() => {
|
|
2215
2035
|
if (!ref.current || !autoStart) return;
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
startRef.current();
|
|
2221
|
-
if (triggerOnce) {
|
|
2222
|
-
observerRef.current?.disconnect();
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
});
|
|
2036
|
+
return observeElement(
|
|
2037
|
+
ref.current,
|
|
2038
|
+
(entry) => {
|
|
2039
|
+
if (entry.isIntersecting) startRef.current();
|
|
2226
2040
|
},
|
|
2227
|
-
{ threshold }
|
|
2041
|
+
{ threshold },
|
|
2042
|
+
triggerOnce
|
|
2228
2043
|
);
|
|
2229
|
-
observerRef.current.observe(ref.current);
|
|
2230
|
-
return () => {
|
|
2231
|
-
if (observerRef.current) {
|
|
2232
|
-
observerRef.current.disconnect();
|
|
2233
|
-
}
|
|
2234
|
-
};
|
|
2235
2044
|
}, [autoStart, threshold, triggerOnce]);
|
|
2236
2045
|
useEffect(() => {
|
|
2237
2046
|
return () => {
|
|
@@ -2259,15 +2068,15 @@ function useScaleIn(options = {}) {
|
|
|
2259
2068
|
};
|
|
2260
2069
|
}
|
|
2261
2070
|
function useBounceIn(options = {}) {
|
|
2071
|
+
const profile = useMotionProfile();
|
|
2262
2072
|
const {
|
|
2263
2073
|
duration = 600,
|
|
2264
2074
|
delay = 0,
|
|
2265
2075
|
autoStart = true,
|
|
2266
|
-
intensity =
|
|
2267
|
-
threshold =
|
|
2268
|
-
triggerOnce =
|
|
2269
|
-
easing: easing2 =
|
|
2270
|
-
// 바운스 이징
|
|
2076
|
+
intensity = profile.entrance.bounce.intensity,
|
|
2077
|
+
threshold = profile.base.threshold,
|
|
2078
|
+
triggerOnce = profile.base.triggerOnce,
|
|
2079
|
+
easing: easing2 = profile.entrance.bounce.easing,
|
|
2271
2080
|
onComplete,
|
|
2272
2081
|
onStart,
|
|
2273
2082
|
onStop,
|
|
@@ -2279,7 +2088,6 @@ function useBounceIn(options = {}) {
|
|
|
2279
2088
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
2280
2089
|
const [isVisible, setIsVisible] = useState(autoStart ? false : true);
|
|
2281
2090
|
const [progress, setProgress] = useState(autoStart ? 0 : 1);
|
|
2282
|
-
const observerRef = useRef(null);
|
|
2283
2091
|
const timeoutRef = useRef(null);
|
|
2284
2092
|
const bounceTimeoutRef = useRef(null);
|
|
2285
2093
|
const startRef = useRef(() => {
|
|
@@ -2336,25 +2144,14 @@ function useBounceIn(options = {}) {
|
|
|
2336
2144
|
}, [stop, onReset]);
|
|
2337
2145
|
useEffect(() => {
|
|
2338
2146
|
if (!ref.current || !autoStart) return;
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
startRef.current();
|
|
2344
|
-
if (triggerOnce) {
|
|
2345
|
-
observerRef.current?.disconnect();
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
});
|
|
2147
|
+
return observeElement(
|
|
2148
|
+
ref.current,
|
|
2149
|
+
(entry) => {
|
|
2150
|
+
if (entry.isIntersecting) startRef.current();
|
|
2349
2151
|
},
|
|
2350
|
-
{ threshold }
|
|
2152
|
+
{ threshold },
|
|
2153
|
+
triggerOnce
|
|
2351
2154
|
);
|
|
2352
|
-
observerRef.current.observe(ref.current);
|
|
2353
|
-
return () => {
|
|
2354
|
-
if (observerRef.current) {
|
|
2355
|
-
observerRef.current.disconnect();
|
|
2356
|
-
}
|
|
2357
|
-
};
|
|
2358
2155
|
}, [autoStart, threshold, triggerOnce]);
|
|
2359
2156
|
useEffect(() => {
|
|
2360
2157
|
return () => {
|
|
@@ -2679,18 +2476,33 @@ function usePulse(options = {}) {
|
|
|
2679
2476
|
reset
|
|
2680
2477
|
};
|
|
2681
2478
|
}
|
|
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
|
|
2682
2493
|
function useSpringMotion(options) {
|
|
2494
|
+
const profile = useMotionProfile();
|
|
2683
2495
|
const {
|
|
2684
2496
|
from,
|
|
2685
2497
|
to,
|
|
2686
|
-
mass =
|
|
2687
|
-
stiffness =
|
|
2688
|
-
damping =
|
|
2689
|
-
restDelta =
|
|
2690
|
-
restSpeed =
|
|
2498
|
+
mass = profile.spring.mass,
|
|
2499
|
+
stiffness = profile.spring.stiffness,
|
|
2500
|
+
damping = profile.spring.damping,
|
|
2501
|
+
restDelta = profile.spring.restDelta,
|
|
2502
|
+
restSpeed = profile.spring.restSpeed,
|
|
2691
2503
|
onComplete,
|
|
2692
2504
|
enabled = true,
|
|
2693
|
-
autoStart =
|
|
2505
|
+
autoStart = true
|
|
2694
2506
|
} = options;
|
|
2695
2507
|
const ref = useRef(null);
|
|
2696
2508
|
const [springState, setSpringState] = useState({
|
|
@@ -2702,16 +2514,7 @@ function useSpringMotion(options) {
|
|
|
2702
2514
|
const [progress, setProgress] = useState(0);
|
|
2703
2515
|
const motionRef = useRef(null);
|
|
2704
2516
|
const lastTimeRef = useRef(0);
|
|
2705
|
-
const
|
|
2706
|
-
const displacement = currentValue - targetValue;
|
|
2707
|
-
const springForce = -stiffness * displacement;
|
|
2708
|
-
const dampingForce = -damping * currentVelocity;
|
|
2709
|
-
const totalForce = springForce + dampingForce;
|
|
2710
|
-
const acceleration = totalForce / mass;
|
|
2711
|
-
const newVelocity = currentVelocity + acceleration * deltaTime;
|
|
2712
|
-
const newValue = currentValue + newVelocity * deltaTime;
|
|
2713
|
-
return { value: newValue, velocity: newVelocity };
|
|
2714
|
-
}, [mass, stiffness, damping]);
|
|
2517
|
+
const springConfig = useMemo(() => ({ stiffness, damping, mass }), [stiffness, damping, mass]);
|
|
2715
2518
|
const animate = useCallback((currentTime) => {
|
|
2716
2519
|
if (!enabled || !springState.isAnimating) return;
|
|
2717
2520
|
const deltaTime = Math.min(currentTime - lastTimeRef.current, 16) / 1e3;
|
|
@@ -2720,7 +2523,8 @@ function useSpringMotion(options) {
|
|
|
2720
2523
|
springState.value,
|
|
2721
2524
|
springState.velocity,
|
|
2722
2525
|
to,
|
|
2723
|
-
deltaTime
|
|
2526
|
+
deltaTime,
|
|
2527
|
+
springConfig
|
|
2724
2528
|
);
|
|
2725
2529
|
const range = Math.abs(to - from);
|
|
2726
2530
|
const currentProgress = range > 0 ? Math.min(Math.abs(value - from) / range, 1) : 1;
|
|
@@ -2742,7 +2546,7 @@ function useSpringMotion(options) {
|
|
|
2742
2546
|
isAnimating: true
|
|
2743
2547
|
});
|
|
2744
2548
|
motionRef.current = requestAnimationFrame(animate);
|
|
2745
|
-
}, [enabled, springState.isAnimating, to, from, restDelta, restSpeed, onComplete,
|
|
2549
|
+
}, [enabled, springState.isAnimating, to, from, restDelta, restSpeed, onComplete, springConfig]);
|
|
2746
2550
|
const start = useCallback(() => {
|
|
2747
2551
|
if (springState.isAnimating) return;
|
|
2748
2552
|
setSpringState((prev) => ({
|
|
@@ -2890,11 +2694,12 @@ function useGradient(options = {}) {
|
|
|
2890
2694
|
};
|
|
2891
2695
|
}
|
|
2892
2696
|
function useHoverMotion(options = {}) {
|
|
2697
|
+
const profile = useMotionProfile();
|
|
2893
2698
|
const {
|
|
2894
|
-
duration =
|
|
2895
|
-
easing: easing2 =
|
|
2896
|
-
hoverScale =
|
|
2897
|
-
hoverY =
|
|
2699
|
+
duration = profile.interaction.hover.duration,
|
|
2700
|
+
easing: easing2 = profile.interaction.hover.easing,
|
|
2701
|
+
hoverScale = profile.interaction.hover.scale,
|
|
2702
|
+
hoverY = profile.interaction.hover.y,
|
|
2898
2703
|
hoverOpacity = 1
|
|
2899
2704
|
} = options;
|
|
2900
2705
|
const ref = useRef(null);
|
|
@@ -3178,13 +2983,14 @@ function useFocusToggle(options = {}) {
|
|
|
3178
2983
|
};
|
|
3179
2984
|
}
|
|
3180
2985
|
function useScrollReveal(options = {}) {
|
|
2986
|
+
const profile = useMotionProfile();
|
|
3181
2987
|
const {
|
|
3182
|
-
threshold =
|
|
2988
|
+
threshold = profile.base.threshold,
|
|
3183
2989
|
rootMargin = "0px",
|
|
3184
|
-
triggerOnce =
|
|
2990
|
+
triggerOnce = profile.base.triggerOnce,
|
|
3185
2991
|
delay = 0,
|
|
3186
|
-
duration =
|
|
3187
|
-
easing: easing2 =
|
|
2992
|
+
duration = profile.base.duration,
|
|
2993
|
+
easing: easing2 = profile.base.easing,
|
|
3188
2994
|
motionType = "fadeIn",
|
|
3189
2995
|
onComplete,
|
|
3190
2996
|
onStart,
|
|
@@ -3213,15 +3019,14 @@ function useScrollReveal(options = {}) {
|
|
|
3213
3019
|
}, [triggerOnce, hasTriggered, delay, onStart, onComplete]);
|
|
3214
3020
|
useEffect(() => {
|
|
3215
3021
|
if (!ref.current) return;
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
return () => {
|
|
3222
|
-
observer.disconnect();
|
|
3223
|
-
};
|
|
3022
|
+
return observeElement(
|
|
3023
|
+
ref.current,
|
|
3024
|
+
(entry) => observerCallback([entry]),
|
|
3025
|
+
{ threshold, rootMargin }
|
|
3026
|
+
);
|
|
3224
3027
|
}, [observerCallback, threshold, rootMargin]);
|
|
3028
|
+
const slideDistance = profile.entrance.slide.distance;
|
|
3029
|
+
const scaleFrom = profile.entrance.scale.from;
|
|
3225
3030
|
const style = useMemo(() => {
|
|
3226
3031
|
const baseTransition = `all ${duration}ms ${easing2}`;
|
|
3227
3032
|
if (!isVisible) {
|
|
@@ -3234,25 +3039,25 @@ function useScrollReveal(options = {}) {
|
|
|
3234
3039
|
case "slideUp":
|
|
3235
3040
|
return {
|
|
3236
3041
|
opacity: 0,
|
|
3237
|
-
transform:
|
|
3042
|
+
transform: `translateY(${slideDistance}px)`,
|
|
3238
3043
|
transition: baseTransition
|
|
3239
3044
|
};
|
|
3240
3045
|
case "slideLeft":
|
|
3241
3046
|
return {
|
|
3242
3047
|
opacity: 0,
|
|
3243
|
-
transform:
|
|
3048
|
+
transform: `translateX(-${slideDistance}px)`,
|
|
3244
3049
|
transition: baseTransition
|
|
3245
3050
|
};
|
|
3246
3051
|
case "slideRight":
|
|
3247
3052
|
return {
|
|
3248
3053
|
opacity: 0,
|
|
3249
|
-
transform:
|
|
3054
|
+
transform: `translateX(${slideDistance}px)`,
|
|
3250
3055
|
transition: baseTransition
|
|
3251
3056
|
};
|
|
3252
3057
|
case "scaleIn":
|
|
3253
3058
|
return {
|
|
3254
3059
|
opacity: 0,
|
|
3255
|
-
transform:
|
|
3060
|
+
transform: `scale(${scaleFrom})`,
|
|
3256
3061
|
transition: baseTransition
|
|
3257
3062
|
};
|
|
3258
3063
|
case "bounceIn":
|
|
@@ -3273,7 +3078,7 @@ function useScrollReveal(options = {}) {
|
|
|
3273
3078
|
transform: "none",
|
|
3274
3079
|
transition: baseTransition
|
|
3275
3080
|
};
|
|
3276
|
-
}, [isVisible, motionType, duration, easing2]);
|
|
3081
|
+
}, [isVisible, motionType, duration, easing2, slideDistance, scaleFrom]);
|
|
3277
3082
|
const start = useCallback(() => {
|
|
3278
3083
|
setIsAnimating(true);
|
|
3279
3084
|
onStart?.();
|
|
@@ -3306,6 +3111,38 @@ function useScrollReveal(options = {}) {
|
|
|
3306
3111
|
stop
|
|
3307
3112
|
};
|
|
3308
3113
|
}
|
|
3114
|
+
|
|
3115
|
+
// src/utils/sharedScroll.ts
|
|
3116
|
+
var subscribers = /* @__PURE__ */ new Set();
|
|
3117
|
+
var listening = false;
|
|
3118
|
+
var rafId = 0;
|
|
3119
|
+
function tick() {
|
|
3120
|
+
subscribers.forEach((cb) => cb());
|
|
3121
|
+
}
|
|
3122
|
+
function onScroll() {
|
|
3123
|
+
cancelAnimationFrame(rafId);
|
|
3124
|
+
rafId = requestAnimationFrame(tick);
|
|
3125
|
+
}
|
|
3126
|
+
function subscribeScroll(cb) {
|
|
3127
|
+
subscribers.add(cb);
|
|
3128
|
+
if (!listening && subscribers.size > 0) {
|
|
3129
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
3130
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
3131
|
+
listening = true;
|
|
3132
|
+
}
|
|
3133
|
+
return () => {
|
|
3134
|
+
subscribers.delete(cb);
|
|
3135
|
+
if (listening && subscribers.size === 0) {
|
|
3136
|
+
window.removeEventListener("scroll", onScroll);
|
|
3137
|
+
window.removeEventListener("resize", onScroll);
|
|
3138
|
+
cancelAnimationFrame(rafId);
|
|
3139
|
+
listening = false;
|
|
3140
|
+
}
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
// src/hooks/useScrollProgress.ts
|
|
3145
|
+
var PROGRESS_THRESHOLD = 0.1;
|
|
3309
3146
|
function useScrollProgress(options = {}) {
|
|
3310
3147
|
const {
|
|
3311
3148
|
target,
|
|
@@ -3314,27 +3151,24 @@ function useScrollProgress(options = {}) {
|
|
|
3314
3151
|
} = options;
|
|
3315
3152
|
const [progress, setProgress] = useState(showOnMount ? 0 : 0);
|
|
3316
3153
|
const [mounted, setMounted] = useState(false);
|
|
3154
|
+
const progressRef = useRef(progress);
|
|
3317
3155
|
useEffect(() => {
|
|
3318
3156
|
setMounted(true);
|
|
3319
3157
|
}, []);
|
|
3320
3158
|
useEffect(() => {
|
|
3321
3159
|
if (!mounted) return;
|
|
3322
3160
|
const calculateProgress = () => {
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3161
|
+
const scrollTop = window.pageYOffset;
|
|
3162
|
+
const scrollHeight = target || document.documentElement.scrollHeight - window.innerHeight;
|
|
3163
|
+
const adjustedScrollTop = Math.max(0, scrollTop - offset);
|
|
3164
|
+
const next = Math.min(100, Math.max(0, adjustedScrollTop / scrollHeight * 100));
|
|
3165
|
+
if (Math.abs(next - progressRef.current) > PROGRESS_THRESHOLD) {
|
|
3166
|
+
progressRef.current = next;
|
|
3167
|
+
setProgress(next);
|
|
3329
3168
|
}
|
|
3330
3169
|
};
|
|
3331
3170
|
calculateProgress();
|
|
3332
|
-
|
|
3333
|
-
window.addEventListener("resize", calculateProgress, { passive: true });
|
|
3334
|
-
return () => {
|
|
3335
|
-
window.removeEventListener("scroll", calculateProgress);
|
|
3336
|
-
window.removeEventListener("resize", calculateProgress);
|
|
3337
|
-
};
|
|
3171
|
+
return subscribeScroll(calculateProgress);
|
|
3338
3172
|
}, [target, offset, mounted]);
|
|
3339
3173
|
return {
|
|
3340
3174
|
progress,
|
|
@@ -3719,14 +3553,11 @@ function useInView(options = {}) {
|
|
|
3719
3553
|
useEffect(() => {
|
|
3720
3554
|
const element = ref.current;
|
|
3721
3555
|
if (!element) return;
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
return () => {
|
|
3728
|
-
observer.disconnect();
|
|
3729
|
-
};
|
|
3556
|
+
return observeElement(
|
|
3557
|
+
element,
|
|
3558
|
+
(entry2) => handleIntersect([entry2]),
|
|
3559
|
+
{ threshold, rootMargin }
|
|
3560
|
+
);
|
|
3730
3561
|
}, [threshold, rootMargin, handleIntersect]);
|
|
3731
3562
|
return {
|
|
3732
3563
|
ref,
|
|
@@ -4122,170 +3953,1432 @@ function useGesture(options = {}) {
|
|
|
4122
3953
|
clearTimeout(longPressRef.current);
|
|
4123
3954
|
}
|
|
4124
3955
|
};
|
|
4125
|
-
}, []);
|
|
3956
|
+
}, []);
|
|
3957
|
+
return {
|
|
3958
|
+
isActive,
|
|
3959
|
+
gesture,
|
|
3960
|
+
scale,
|
|
3961
|
+
rotation,
|
|
3962
|
+
deltaX,
|
|
3963
|
+
deltaY,
|
|
3964
|
+
distance,
|
|
3965
|
+
velocity,
|
|
3966
|
+
start,
|
|
3967
|
+
stop,
|
|
3968
|
+
reset,
|
|
3969
|
+
onTouchStart,
|
|
3970
|
+
onTouchMove,
|
|
3971
|
+
onTouchEnd,
|
|
3972
|
+
onMouseDown,
|
|
3973
|
+
onMouseMove,
|
|
3974
|
+
onMouseUp
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
function useGestureMotion(options) {
|
|
3978
|
+
const {
|
|
3979
|
+
gestureType,
|
|
3980
|
+
duration = 300,
|
|
3981
|
+
easing: easing2 = "ease-out",
|
|
3982
|
+
sensitivity = 1,
|
|
3983
|
+
enabled = true,
|
|
3984
|
+
onGestureStart,
|
|
3985
|
+
onGestureEnd
|
|
3986
|
+
} = options;
|
|
3987
|
+
const elementRef = useRef(null);
|
|
3988
|
+
const [gestureState, setGestureState] = useState({
|
|
3989
|
+
isActive: false,
|
|
3990
|
+
x: 0,
|
|
3991
|
+
y: 0,
|
|
3992
|
+
deltaX: 0,
|
|
3993
|
+
deltaY: 0,
|
|
3994
|
+
scale: 1,
|
|
3995
|
+
rotation: 0
|
|
3996
|
+
});
|
|
3997
|
+
const [motionStyle, setMotionStyle] = useState({});
|
|
3998
|
+
const startPoint = useRef({ x: 0, y: 0 });
|
|
3999
|
+
const isDragging = useRef(false);
|
|
4000
|
+
const updateMotionStyle = useCallback(() => {
|
|
4001
|
+
if (!enabled) return;
|
|
4002
|
+
const { isActive, deltaX, deltaY, scale, rotation } = gestureState;
|
|
4003
|
+
let transform = "";
|
|
4004
|
+
switch (gestureType) {
|
|
4005
|
+
case "hover":
|
|
4006
|
+
transform = isActive ? `scale(${1 + sensitivity * 0.05}) translateY(-${sensitivity * 2}px)` : "scale(1) translateY(0)";
|
|
4007
|
+
break;
|
|
4008
|
+
case "drag":
|
|
4009
|
+
transform = isActive ? `translate(${deltaX * sensitivity}px, ${deltaY * sensitivity}px)` : "translate(0, 0)";
|
|
4010
|
+
break;
|
|
4011
|
+
case "pinch":
|
|
4012
|
+
transform = `scale(${scale})`;
|
|
4013
|
+
break;
|
|
4014
|
+
case "swipe":
|
|
4015
|
+
transform = isActive ? `translateX(${deltaX * sensitivity}px) rotateY(${deltaX * 0.1}deg)` : "translateX(0) rotateY(0)";
|
|
4016
|
+
break;
|
|
4017
|
+
case "tilt":
|
|
4018
|
+
transform = isActive ? `rotateX(${deltaY * 0.1}deg) rotateY(${deltaX * 0.1}deg)` : "rotateX(0) rotateY(0)";
|
|
4019
|
+
break;
|
|
4020
|
+
}
|
|
4021
|
+
setMotionStyle({
|
|
4022
|
+
transform,
|
|
4023
|
+
transition: isActive ? "none" : `all ${duration}ms ${easing2}`,
|
|
4024
|
+
cursor: gestureType === "drag" && isActive ? "grabbing" : "pointer"
|
|
4025
|
+
});
|
|
4026
|
+
}, [gestureState, gestureType, enabled, duration, easing2, sensitivity]);
|
|
4027
|
+
const handleMouseDown = useCallback((e) => {
|
|
4028
|
+
if (!enabled || gestureType !== "drag") return;
|
|
4029
|
+
isDragging.current = true;
|
|
4030
|
+
startPoint.current = { x: e.clientX, y: e.clientY };
|
|
4031
|
+
setGestureState((prev) => ({ ...prev, isActive: true }));
|
|
4032
|
+
onGestureStart?.();
|
|
4033
|
+
}, [enabled, gestureType, onGestureStart]);
|
|
4034
|
+
const handleMouseMove = useCallback((e) => {
|
|
4035
|
+
if (!enabled || !isDragging.current) return;
|
|
4036
|
+
const deltaX = e.clientX - startPoint.current.x;
|
|
4037
|
+
const deltaY = e.clientY - startPoint.current.y;
|
|
4038
|
+
setGestureState((prev) => ({
|
|
4039
|
+
...prev,
|
|
4040
|
+
x: e.clientX,
|
|
4041
|
+
y: e.clientY,
|
|
4042
|
+
deltaX,
|
|
4043
|
+
deltaY
|
|
4044
|
+
}));
|
|
4045
|
+
}, [enabled]);
|
|
4046
|
+
const handleMouseUp = useCallback(() => {
|
|
4047
|
+
if (!enabled) return;
|
|
4048
|
+
isDragging.current = false;
|
|
4049
|
+
setGestureState((prev) => ({ ...prev, isActive: false }));
|
|
4050
|
+
onGestureEnd?.();
|
|
4051
|
+
}, [enabled, onGestureEnd]);
|
|
4052
|
+
const handleMouseEnter = useCallback(() => {
|
|
4053
|
+
if (!enabled || gestureType !== "hover") return;
|
|
4054
|
+
setGestureState((prev) => ({ ...prev, isActive: true }));
|
|
4055
|
+
onGestureStart?.();
|
|
4056
|
+
}, [enabled, gestureType, onGestureStart]);
|
|
4057
|
+
const handleMouseLeave = useCallback(() => {
|
|
4058
|
+
if (!enabled || gestureType !== "hover") return;
|
|
4059
|
+
setGestureState((prev) => ({ ...prev, isActive: false }));
|
|
4060
|
+
onGestureEnd?.();
|
|
4061
|
+
}, [enabled, gestureType, onGestureEnd]);
|
|
4062
|
+
const handleTouchStart = useCallback((e) => {
|
|
4063
|
+
if (!enabled) return;
|
|
4064
|
+
const touch = e.touches[0];
|
|
4065
|
+
startPoint.current = { x: touch.clientX, y: touch.clientY };
|
|
4066
|
+
setGestureState((prev) => ({ ...prev, isActive: true }));
|
|
4067
|
+
onGestureStart?.();
|
|
4068
|
+
}, [enabled, onGestureStart]);
|
|
4069
|
+
const handleTouchMove = useCallback((e) => {
|
|
4070
|
+
if (!enabled) return;
|
|
4071
|
+
const touch = e.touches[0];
|
|
4072
|
+
const deltaX = touch.clientX - startPoint.current.x;
|
|
4073
|
+
const deltaY = touch.clientY - startPoint.current.y;
|
|
4074
|
+
setGestureState((prev) => ({
|
|
4075
|
+
...prev,
|
|
4076
|
+
x: touch.clientX,
|
|
4077
|
+
y: touch.clientY,
|
|
4078
|
+
deltaX,
|
|
4079
|
+
deltaY
|
|
4080
|
+
}));
|
|
4081
|
+
}, [enabled]);
|
|
4082
|
+
const handleTouchEnd = useCallback(() => {
|
|
4083
|
+
if (!enabled) return;
|
|
4084
|
+
setGestureState((prev) => ({ ...prev, isActive: false }));
|
|
4085
|
+
onGestureEnd?.();
|
|
4086
|
+
}, [enabled, onGestureEnd]);
|
|
4087
|
+
useEffect(() => {
|
|
4088
|
+
if (!elementRef.current) return;
|
|
4089
|
+
const element = elementRef.current;
|
|
4090
|
+
if (gestureType === "hover") {
|
|
4091
|
+
element.addEventListener("mouseenter", handleMouseEnter);
|
|
4092
|
+
element.addEventListener("mouseleave", handleMouseLeave);
|
|
4093
|
+
} else if (gestureType === "drag") {
|
|
4094
|
+
element.addEventListener("mousedown", handleMouseDown);
|
|
4095
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
4096
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
4097
|
+
}
|
|
4098
|
+
element.addEventListener("touchstart", handleTouchStart);
|
|
4099
|
+
element.addEventListener("touchmove", handleTouchMove);
|
|
4100
|
+
element.addEventListener("touchend", handleTouchEnd);
|
|
4101
|
+
return () => {
|
|
4102
|
+
element.removeEventListener("mouseenter", handleMouseEnter);
|
|
4103
|
+
element.removeEventListener("mouseleave", handleMouseLeave);
|
|
4104
|
+
element.removeEventListener("mousedown", handleMouseDown);
|
|
4105
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
4106
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
4107
|
+
element.removeEventListener("touchstart", handleTouchStart);
|
|
4108
|
+
element.removeEventListener("touchmove", handleTouchMove);
|
|
4109
|
+
element.removeEventListener("touchend", handleTouchEnd);
|
|
4110
|
+
};
|
|
4111
|
+
}, [gestureType, handleMouseEnter, handleMouseLeave, handleMouseDown, handleMouseMove, handleMouseUp, handleTouchStart, handleTouchMove, handleTouchEnd]);
|
|
4112
|
+
useEffect(() => {
|
|
4113
|
+
updateMotionStyle();
|
|
4114
|
+
}, [updateMotionStyle]);
|
|
4115
|
+
return {
|
|
4116
|
+
ref: elementRef,
|
|
4117
|
+
gestureState,
|
|
4118
|
+
motionStyle,
|
|
4119
|
+
isActive: gestureState.isActive
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
function useButtonEffect(options = {}) {
|
|
4123
|
+
const {
|
|
4124
|
+
duration = 200,
|
|
4125
|
+
easing: easing2 = "ease-out",
|
|
4126
|
+
type = "scale",
|
|
4127
|
+
scaleAmount = 0.95,
|
|
4128
|
+
rippleColor = "rgba(255, 255, 255, 0.6)",
|
|
4129
|
+
rippleSize = 100,
|
|
4130
|
+
rippleDuration = 600,
|
|
4131
|
+
glowColor = "#3b82f6",
|
|
4132
|
+
glowSize = 20,
|
|
4133
|
+
glowIntensity = 0.8,
|
|
4134
|
+
shakeAmount = 5,
|
|
4135
|
+
shakeFrequency = 10,
|
|
4136
|
+
bounceHeight = 10,
|
|
4137
|
+
bounceStiffness = 0.3,
|
|
4138
|
+
slideDistance = 5,
|
|
4139
|
+
slideDirection = "down",
|
|
4140
|
+
hoverScale = 1.05,
|
|
4141
|
+
hoverRotate = 0,
|
|
4142
|
+
hoverTranslateY = -2,
|
|
4143
|
+
hoverTranslateX = 0,
|
|
4144
|
+
activeScale = 0.95,
|
|
4145
|
+
activeRotate = 0,
|
|
4146
|
+
activeTranslateY = 2,
|
|
4147
|
+
activeTranslateX = 0,
|
|
4148
|
+
focusScale = 1.02,
|
|
4149
|
+
focusGlow = true,
|
|
4150
|
+
disabled = false,
|
|
4151
|
+
disabledOpacity = 0.5,
|
|
4152
|
+
autoStart = false,
|
|
4153
|
+
onComplete,
|
|
4154
|
+
onStart,
|
|
4155
|
+
onStop,
|
|
4156
|
+
onReset
|
|
4157
|
+
} = options;
|
|
4158
|
+
const ref = useRef(null);
|
|
4159
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
4160
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
4161
|
+
const [progress, setProgress] = useState(0);
|
|
4162
|
+
const [buttonType] = useState(type);
|
|
4163
|
+
const [isPressed, setIsPressed] = useState(false);
|
|
4164
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
4165
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
4166
|
+
const [ripplePosition, setRipplePosition] = useState({ x: 0, y: 0 });
|
|
4167
|
+
const [currentGlowIntensity, setGlowIntensity] = useState(0);
|
|
4168
|
+
const [shakeOffset, setShakeOffset] = useState(0);
|
|
4169
|
+
const [bounceOffset, setBounceOffset] = useState(0);
|
|
4170
|
+
const [slideOffset, setSlideOffset] = useState(0);
|
|
4171
|
+
const animationRef = useRef(null);
|
|
4172
|
+
const startTimeRef = useRef(0);
|
|
4173
|
+
useCallback((event) => {
|
|
4174
|
+
if (!ref.current) return;
|
|
4175
|
+
const rect = ref.current.getBoundingClientRect();
|
|
4176
|
+
const x = event.clientX - rect.left;
|
|
4177
|
+
const y = event.clientY - rect.top;
|
|
4178
|
+
setRipplePosition({ x, y });
|
|
4179
|
+
setIsAnimating(true);
|
|
4180
|
+
setProgress(0);
|
|
4181
|
+
startTimeRef.current = Date.now();
|
|
4182
|
+
onStart?.();
|
|
4183
|
+
const animateRipple = () => {
|
|
4184
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
4185
|
+
const currentProgress = Math.min(elapsed / rippleDuration, 1);
|
|
4186
|
+
setProgress(currentProgress);
|
|
4187
|
+
if (currentProgress < 1) {
|
|
4188
|
+
animationRef.current = requestAnimationFrame(animateRipple);
|
|
4189
|
+
} else {
|
|
4190
|
+
setIsAnimating(false);
|
|
4191
|
+
onComplete?.();
|
|
4192
|
+
}
|
|
4193
|
+
};
|
|
4194
|
+
animationRef.current = requestAnimationFrame(animateRipple);
|
|
4195
|
+
}, [rippleDuration, onStart, onComplete]);
|
|
4196
|
+
const shakeButton = useCallback(() => {
|
|
4197
|
+
setIsAnimating(true);
|
|
4198
|
+
setProgress(0);
|
|
4199
|
+
startTimeRef.current = Date.now();
|
|
4200
|
+
onStart?.();
|
|
4201
|
+
const animateShake = () => {
|
|
4202
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
4203
|
+
const currentProgress = Math.min(elapsed / duration, 1);
|
|
4204
|
+
setProgress(currentProgress);
|
|
4205
|
+
const shake = shakeAmount * Math.sin(currentProgress * Math.PI * 2 * shakeFrequency) * (1 - currentProgress);
|
|
4206
|
+
setShakeOffset(shake);
|
|
4207
|
+
if (currentProgress < 1) {
|
|
4208
|
+
animationRef.current = requestAnimationFrame(animateShake);
|
|
4209
|
+
} else {
|
|
4210
|
+
setIsAnimating(false);
|
|
4211
|
+
setShakeOffset(0);
|
|
4212
|
+
onComplete?.();
|
|
4213
|
+
}
|
|
4214
|
+
};
|
|
4215
|
+
animationRef.current = requestAnimationFrame(animateShake);
|
|
4216
|
+
}, [duration, shakeAmount, shakeFrequency, onStart, onComplete]);
|
|
4217
|
+
const bounceButton = useCallback(() => {
|
|
4218
|
+
setIsAnimating(true);
|
|
4219
|
+
setProgress(0);
|
|
4220
|
+
startTimeRef.current = Date.now();
|
|
4221
|
+
onStart?.();
|
|
4222
|
+
const animateBounce = () => {
|
|
4223
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
4224
|
+
const currentProgress = Math.min(elapsed / duration, 1);
|
|
4225
|
+
setProgress(currentProgress);
|
|
4226
|
+
const bounce = bounceHeight * Math.sin(currentProgress * Math.PI * bounceStiffness) * Math.exp(-currentProgress * 3);
|
|
4227
|
+
setBounceOffset(bounce);
|
|
4228
|
+
if (currentProgress < 1) {
|
|
4229
|
+
animationRef.current = requestAnimationFrame(animateBounce);
|
|
4230
|
+
} else {
|
|
4231
|
+
setIsAnimating(false);
|
|
4232
|
+
setBounceOffset(0);
|
|
4233
|
+
onComplete?.();
|
|
4234
|
+
}
|
|
4235
|
+
};
|
|
4236
|
+
animationRef.current = requestAnimationFrame(animateBounce);
|
|
4237
|
+
}, [duration, bounceHeight, bounceStiffness, onStart, onComplete]);
|
|
4238
|
+
const slideButton = useCallback(() => {
|
|
4239
|
+
setIsAnimating(true);
|
|
4240
|
+
setProgress(0);
|
|
4241
|
+
startTimeRef.current = Date.now();
|
|
4242
|
+
onStart?.();
|
|
4243
|
+
const animateSlide = () => {
|
|
4244
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
4245
|
+
const currentProgress = Math.min(elapsed / duration, 1);
|
|
4246
|
+
setProgress(currentProgress);
|
|
4247
|
+
const slide = slideDistance * currentProgress;
|
|
4248
|
+
setSlideOffset(slide);
|
|
4249
|
+
if (currentProgress < 1) {
|
|
4250
|
+
animationRef.current = requestAnimationFrame(animateSlide);
|
|
4251
|
+
} else {
|
|
4252
|
+
setIsAnimating(false);
|
|
4253
|
+
onComplete?.();
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
animationRef.current = requestAnimationFrame(animateSlide);
|
|
4257
|
+
}, [duration, slideDistance, onStart, onComplete]);
|
|
4258
|
+
const pressButton = useCallback(() => {
|
|
4259
|
+
if (disabled) return;
|
|
4260
|
+
setIsPressed(true);
|
|
4261
|
+
switch (type) {
|
|
4262
|
+
case "ripple":
|
|
4263
|
+
break;
|
|
4264
|
+
case "shake":
|
|
4265
|
+
shakeButton();
|
|
4266
|
+
break;
|
|
4267
|
+
case "bounce":
|
|
4268
|
+
bounceButton();
|
|
4269
|
+
break;
|
|
4270
|
+
case "slide":
|
|
4271
|
+
slideButton();
|
|
4272
|
+
break;
|
|
4273
|
+
}
|
|
4274
|
+
}, [disabled, type, shakeButton, bounceButton, slideButton]);
|
|
4275
|
+
const releaseButton = useCallback(() => {
|
|
4276
|
+
setIsPressed(false);
|
|
4277
|
+
}, []);
|
|
4278
|
+
const setButtonState = useCallback((state) => {
|
|
4279
|
+
switch (state) {
|
|
4280
|
+
case "hover":
|
|
4281
|
+
setIsHovered(true);
|
|
4282
|
+
break;
|
|
4283
|
+
case "active":
|
|
4284
|
+
setIsPressed(true);
|
|
4285
|
+
break;
|
|
4286
|
+
case "focus":
|
|
4287
|
+
setIsFocused(true);
|
|
4288
|
+
break;
|
|
4289
|
+
case "disabled":
|
|
4290
|
+
setIsHovered(false);
|
|
4291
|
+
setIsPressed(false);
|
|
4292
|
+
setIsFocused(false);
|
|
4293
|
+
break;
|
|
4294
|
+
default:
|
|
4295
|
+
setIsHovered(false);
|
|
4296
|
+
setIsPressed(false);
|
|
4297
|
+
setIsFocused(false);
|
|
4298
|
+
break;
|
|
4299
|
+
}
|
|
4300
|
+
}, []);
|
|
4301
|
+
const start = useCallback(() => {
|
|
4302
|
+
if (!isVisible) {
|
|
4303
|
+
setIsVisible(true);
|
|
4304
|
+
}
|
|
4305
|
+
}, [isVisible]);
|
|
4306
|
+
const stop = useCallback(() => {
|
|
4307
|
+
setIsAnimating(false);
|
|
4308
|
+
if (animationRef.current) {
|
|
4309
|
+
cancelAnimationFrame(animationRef.current);
|
|
4310
|
+
animationRef.current = null;
|
|
4311
|
+
}
|
|
4312
|
+
onStop?.();
|
|
4313
|
+
}, [onStop]);
|
|
4314
|
+
const reset = useCallback(() => {
|
|
4315
|
+
setIsVisible(false);
|
|
4316
|
+
setIsAnimating(false);
|
|
4317
|
+
setProgress(0);
|
|
4318
|
+
setIsPressed(false);
|
|
4319
|
+
setIsHovered(false);
|
|
4320
|
+
setIsFocused(false);
|
|
4321
|
+
setRipplePosition({ x: 0, y: 0 });
|
|
4322
|
+
setGlowIntensity(0);
|
|
4323
|
+
setShakeOffset(0);
|
|
4324
|
+
setBounceOffset(0);
|
|
4325
|
+
setSlideOffset(0);
|
|
4326
|
+
startTimeRef.current = 0;
|
|
4327
|
+
if (animationRef.current) {
|
|
4328
|
+
cancelAnimationFrame(animationRef.current);
|
|
4329
|
+
animationRef.current = null;
|
|
4330
|
+
}
|
|
4331
|
+
onReset?.();
|
|
4332
|
+
}, [onReset]);
|
|
4333
|
+
const pause = useCallback(() => {
|
|
4334
|
+
setIsAnimating(false);
|
|
4335
|
+
if (animationRef.current) {
|
|
4336
|
+
cancelAnimationFrame(animationRef.current);
|
|
4337
|
+
animationRef.current = null;
|
|
4338
|
+
}
|
|
4339
|
+
}, []);
|
|
4340
|
+
const resume = useCallback(() => {
|
|
4341
|
+
if (isVisible && !isAnimating) {
|
|
4342
|
+
setIsAnimating(true);
|
|
4343
|
+
}
|
|
4344
|
+
}, [isVisible, isAnimating]);
|
|
4345
|
+
useEffect(() => {
|
|
4346
|
+
if (autoStart) {
|
|
4347
|
+
start();
|
|
4348
|
+
}
|
|
4349
|
+
}, [autoStart, start]);
|
|
4350
|
+
useEffect(() => {
|
|
4351
|
+
return () => {
|
|
4352
|
+
if (animationRef.current) {
|
|
4353
|
+
cancelAnimationFrame(animationRef.current);
|
|
4354
|
+
}
|
|
4355
|
+
};
|
|
4356
|
+
}, []);
|
|
4357
|
+
const getButtonStyle = () => {
|
|
4358
|
+
let scale = 1;
|
|
4359
|
+
let rotate = 0;
|
|
4360
|
+
let translateY = 0;
|
|
4361
|
+
let translateX = 0;
|
|
4362
|
+
let boxShadow = "none";
|
|
4363
|
+
if (isPressed) {
|
|
4364
|
+
scale = activeScale;
|
|
4365
|
+
rotate = activeRotate;
|
|
4366
|
+
translateY = activeTranslateY;
|
|
4367
|
+
translateX = activeTranslateX;
|
|
4368
|
+
} else if (isHovered) {
|
|
4369
|
+
scale = hoverScale;
|
|
4370
|
+
rotate = hoverRotate;
|
|
4371
|
+
translateY = hoverTranslateY;
|
|
4372
|
+
translateX = hoverTranslateX;
|
|
4373
|
+
}
|
|
4374
|
+
if (isFocused && focusGlow) {
|
|
4375
|
+
boxShadow = `0 0 ${glowSize}px ${glowColor}`;
|
|
4376
|
+
}
|
|
4377
|
+
switch (type) {
|
|
4378
|
+
case "shake":
|
|
4379
|
+
translateX += shakeOffset;
|
|
4380
|
+
break;
|
|
4381
|
+
case "bounce":
|
|
4382
|
+
translateY += bounceOffset;
|
|
4383
|
+
break;
|
|
4384
|
+
case "slide":
|
|
4385
|
+
if (slideDirection === "left") translateX -= slideOffset;
|
|
4386
|
+
else if (slideDirection === "right") translateX += slideOffset;
|
|
4387
|
+
else if (slideDirection === "up") translateY -= slideOffset;
|
|
4388
|
+
else if (slideDirection === "down") translateY += slideOffset;
|
|
4389
|
+
break;
|
|
4390
|
+
}
|
|
4391
|
+
const baseStyle = {
|
|
4392
|
+
transform: `
|
|
4393
|
+
scale(${scale})
|
|
4394
|
+
rotate(${rotate}deg)
|
|
4395
|
+
translate(${translateX}px, ${translateY}px)
|
|
4396
|
+
`,
|
|
4397
|
+
boxShadow,
|
|
4398
|
+
opacity: disabled ? disabledOpacity : 1,
|
|
4399
|
+
transition: `all ${duration}ms ${easing2}`,
|
|
4400
|
+
willChange: "transform, box-shadow, opacity",
|
|
4401
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
4402
|
+
position: "relative",
|
|
4403
|
+
overflow: "hidden"
|
|
4404
|
+
};
|
|
4405
|
+
return baseStyle;
|
|
4406
|
+
};
|
|
4407
|
+
const style = getButtonStyle();
|
|
4408
|
+
return {
|
|
4409
|
+
ref,
|
|
4410
|
+
isVisible,
|
|
4411
|
+
isAnimating,
|
|
4412
|
+
style,
|
|
4413
|
+
progress,
|
|
4414
|
+
start,
|
|
4415
|
+
stop,
|
|
4416
|
+
reset,
|
|
4417
|
+
pause,
|
|
4418
|
+
resume,
|
|
4419
|
+
buttonType: type,
|
|
4420
|
+
isPressed,
|
|
4421
|
+
isHovered,
|
|
4422
|
+
isFocused,
|
|
4423
|
+
ripplePosition,
|
|
4424
|
+
currentGlowIntensity,
|
|
4425
|
+
shakeOffset,
|
|
4426
|
+
bounceOffset,
|
|
4427
|
+
slideOffset,
|
|
4428
|
+
pressButton,
|
|
4429
|
+
releaseButton,
|
|
4430
|
+
setButtonState
|
|
4431
|
+
};
|
|
4432
|
+
}
|
|
4433
|
+
function useVisibilityToggle(options = {}) {
|
|
4434
|
+
const {
|
|
4435
|
+
duration = 300,
|
|
4436
|
+
easing: easing2 = "ease-out",
|
|
4437
|
+
showScale = 1,
|
|
4438
|
+
showOpacity = 1,
|
|
4439
|
+
showRotate = 0,
|
|
4440
|
+
showTranslateY = 0,
|
|
4441
|
+
showTranslateX = 0,
|
|
4442
|
+
hideScale = 0.8,
|
|
4443
|
+
hideOpacity = 0,
|
|
4444
|
+
hideRotate = 0,
|
|
4445
|
+
hideTranslateY = 20,
|
|
4446
|
+
hideTranslateX = 0,
|
|
4447
|
+
onComplete,
|
|
4448
|
+
onStart,
|
|
4449
|
+
onStop,
|
|
4450
|
+
onReset
|
|
4451
|
+
} = options;
|
|
4452
|
+
const ref = useRef(null);
|
|
4453
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
4454
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
4455
|
+
const [progress, setProgress] = useState(0);
|
|
4456
|
+
const toggle = useCallback(() => {
|
|
4457
|
+
setIsAnimating(true);
|
|
4458
|
+
setProgress(0);
|
|
4459
|
+
onStart?.();
|
|
4460
|
+
const newVisibility = !isVisible;
|
|
4461
|
+
setIsVisible(newVisibility);
|
|
4462
|
+
setTimeout(() => {
|
|
4463
|
+
setIsAnimating(false);
|
|
4464
|
+
setProgress(1);
|
|
4465
|
+
onComplete?.();
|
|
4466
|
+
}, duration);
|
|
4467
|
+
}, [isVisible, duration, onStart, onComplete]);
|
|
4468
|
+
const show = useCallback(() => {
|
|
4469
|
+
if (!isVisible) {
|
|
4470
|
+
setIsAnimating(true);
|
|
4471
|
+
setProgress(0);
|
|
4472
|
+
onStart?.();
|
|
4473
|
+
setIsVisible(true);
|
|
4474
|
+
setTimeout(() => {
|
|
4475
|
+
setIsAnimating(false);
|
|
4476
|
+
setProgress(1);
|
|
4477
|
+
onComplete?.();
|
|
4478
|
+
}, duration);
|
|
4479
|
+
}
|
|
4480
|
+
}, [isVisible, duration, onStart, onComplete]);
|
|
4481
|
+
const hide = useCallback(() => {
|
|
4482
|
+
if (isVisible) {
|
|
4483
|
+
setIsAnimating(true);
|
|
4484
|
+
setProgress(1);
|
|
4485
|
+
onStart?.();
|
|
4486
|
+
setIsVisible(false);
|
|
4487
|
+
setTimeout(() => {
|
|
4488
|
+
setIsAnimating(false);
|
|
4489
|
+
setProgress(0);
|
|
4490
|
+
onComplete?.();
|
|
4491
|
+
}, duration);
|
|
4492
|
+
}
|
|
4493
|
+
}, [isVisible, duration, onStart, onComplete]);
|
|
4494
|
+
const start = useCallback(() => {
|
|
4495
|
+
if (!isVisible) {
|
|
4496
|
+
toggle();
|
|
4497
|
+
}
|
|
4498
|
+
}, [isVisible, toggle]);
|
|
4499
|
+
const stop = useCallback(() => {
|
|
4500
|
+
setIsAnimating(false);
|
|
4501
|
+
onStop?.();
|
|
4502
|
+
}, [onStop]);
|
|
4503
|
+
const reset = useCallback(() => {
|
|
4504
|
+
setIsVisible(false);
|
|
4505
|
+
setIsAnimating(false);
|
|
4506
|
+
setProgress(0);
|
|
4507
|
+
onReset?.();
|
|
4508
|
+
}, [onReset]);
|
|
4509
|
+
const pause = useCallback(() => {
|
|
4510
|
+
setIsAnimating(false);
|
|
4511
|
+
}, []);
|
|
4512
|
+
const resume = useCallback(() => {
|
|
4513
|
+
if (isVisible) {
|
|
4514
|
+
setIsAnimating(true);
|
|
4515
|
+
}
|
|
4516
|
+
}, [isVisible]);
|
|
4517
|
+
const style = {
|
|
4518
|
+
transform: `
|
|
4519
|
+
scale(${isVisible ? showScale : hideScale})
|
|
4520
|
+
rotate(${isVisible ? showRotate : hideRotate}deg)
|
|
4521
|
+
translate(${isVisible ? showTranslateX : hideTranslateX}px, ${isVisible ? showTranslateY : hideTranslateY}px)
|
|
4522
|
+
`,
|
|
4523
|
+
opacity: isVisible ? showOpacity : hideOpacity,
|
|
4524
|
+
transition: `all ${duration}ms ${easing2}`,
|
|
4525
|
+
willChange: "transform, opacity"
|
|
4526
|
+
};
|
|
4527
|
+
return {
|
|
4528
|
+
ref,
|
|
4529
|
+
isVisible,
|
|
4530
|
+
isAnimating,
|
|
4531
|
+
style,
|
|
4532
|
+
progress,
|
|
4533
|
+
start,
|
|
4534
|
+
stop,
|
|
4535
|
+
reset,
|
|
4536
|
+
pause,
|
|
4537
|
+
resume,
|
|
4538
|
+
// 추가 메서드
|
|
4539
|
+
toggle,
|
|
4540
|
+
show,
|
|
4541
|
+
hide
|
|
4542
|
+
};
|
|
4543
|
+
}
|
|
4544
|
+
function useScrollToggle(options = {}) {
|
|
4545
|
+
const {
|
|
4546
|
+
duration = 300,
|
|
4547
|
+
easing: easing2 = "ease-out",
|
|
4548
|
+
showScale = 1,
|
|
4549
|
+
showOpacity = 1,
|
|
4550
|
+
showRotate = 0,
|
|
4551
|
+
showTranslateY = 0,
|
|
4552
|
+
showTranslateX = 0,
|
|
4553
|
+
hideScale = 0.8,
|
|
4554
|
+
hideOpacity = 0,
|
|
4555
|
+
hideRotate = 0,
|
|
4556
|
+
hideTranslateY = 20,
|
|
4557
|
+
hideTranslateX = 0,
|
|
4558
|
+
scrollThreshold = 0.1,
|
|
4559
|
+
scrollDirection = "both",
|
|
4560
|
+
onComplete,
|
|
4561
|
+
onStart,
|
|
4562
|
+
onStop,
|
|
4563
|
+
onReset
|
|
4564
|
+
} = options;
|
|
4565
|
+
const ref = useRef(null);
|
|
4566
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
4567
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
4568
|
+
const [progress, setProgress] = useState(0);
|
|
4569
|
+
const isVisibleRef = useRef(false);
|
|
4570
|
+
const lastScrollYRef = useRef(0);
|
|
4571
|
+
useEffect(() => {
|
|
4572
|
+
isVisibleRef.current = isVisible;
|
|
4573
|
+
}, [isVisible]);
|
|
4574
|
+
const handleScroll = useCallback(() => {
|
|
4575
|
+
if (!ref.current) return;
|
|
4576
|
+
const currentScrollY = window.scrollY;
|
|
4577
|
+
const rect = ref.current.getBoundingClientRect();
|
|
4578
|
+
const threshold = window.innerHeight * scrollThreshold;
|
|
4579
|
+
const isScrollingDown = currentScrollY > lastScrollYRef.current;
|
|
4580
|
+
const isScrollingUp = currentScrollY < lastScrollYRef.current;
|
|
4581
|
+
let shouldToggle = false;
|
|
4582
|
+
if (scrollDirection === "both") {
|
|
4583
|
+
shouldToggle = rect.top <= threshold;
|
|
4584
|
+
} else if (scrollDirection === "down" && isScrollingDown) {
|
|
4585
|
+
shouldToggle = rect.top <= threshold;
|
|
4586
|
+
} else if (scrollDirection === "up" && isScrollingUp) {
|
|
4587
|
+
shouldToggle = rect.top <= threshold;
|
|
4588
|
+
}
|
|
4589
|
+
if (shouldToggle && !isVisibleRef.current) {
|
|
4590
|
+
isVisibleRef.current = true;
|
|
4591
|
+
setIsVisible(true);
|
|
4592
|
+
setIsAnimating(true);
|
|
4593
|
+
setProgress(0);
|
|
4594
|
+
onStart?.();
|
|
4595
|
+
setTimeout(() => {
|
|
4596
|
+
setIsAnimating(false);
|
|
4597
|
+
setProgress(1);
|
|
4598
|
+
onComplete?.();
|
|
4599
|
+
}, duration);
|
|
4600
|
+
} else if (!shouldToggle && isVisibleRef.current) {
|
|
4601
|
+
isVisibleRef.current = false;
|
|
4602
|
+
setIsVisible(false);
|
|
4603
|
+
setIsAnimating(true);
|
|
4604
|
+
setProgress(1);
|
|
4605
|
+
setTimeout(() => {
|
|
4606
|
+
setIsAnimating(false);
|
|
4607
|
+
setProgress(0);
|
|
4608
|
+
}, duration);
|
|
4609
|
+
}
|
|
4610
|
+
lastScrollYRef.current = currentScrollY;
|
|
4611
|
+
}, [scrollDirection, scrollThreshold, duration, onStart, onComplete]);
|
|
4612
|
+
useEffect(() => {
|
|
4613
|
+
handleScroll();
|
|
4614
|
+
return subscribeScroll(handleScroll);
|
|
4615
|
+
}, [handleScroll]);
|
|
4616
|
+
const start = useCallback(() => {
|
|
4617
|
+
if (!isVisibleRef.current) {
|
|
4618
|
+
isVisibleRef.current = true;
|
|
4619
|
+
setIsVisible(true);
|
|
4620
|
+
setIsAnimating(true);
|
|
4621
|
+
setProgress(0);
|
|
4622
|
+
onStart?.();
|
|
4623
|
+
setTimeout(() => {
|
|
4624
|
+
setIsAnimating(false);
|
|
4625
|
+
setProgress(1);
|
|
4626
|
+
onComplete?.();
|
|
4627
|
+
}, duration);
|
|
4628
|
+
}
|
|
4629
|
+
}, [duration, onStart, onComplete]);
|
|
4630
|
+
const stop = useCallback(() => {
|
|
4631
|
+
setIsAnimating(false);
|
|
4632
|
+
onStop?.();
|
|
4633
|
+
}, [onStop]);
|
|
4634
|
+
const reset = useCallback(() => {
|
|
4635
|
+
isVisibleRef.current = false;
|
|
4636
|
+
setIsVisible(false);
|
|
4637
|
+
setIsAnimating(false);
|
|
4638
|
+
setProgress(0);
|
|
4639
|
+
onReset?.();
|
|
4640
|
+
}, [onReset]);
|
|
4641
|
+
const pause = useCallback(() => {
|
|
4642
|
+
setIsAnimating(false);
|
|
4643
|
+
}, []);
|
|
4644
|
+
const resume = useCallback(() => {
|
|
4645
|
+
if (isVisibleRef.current) {
|
|
4646
|
+
setIsAnimating(true);
|
|
4647
|
+
}
|
|
4648
|
+
}, []);
|
|
4649
|
+
const style = {
|
|
4650
|
+
transform: `
|
|
4651
|
+
scale(${isVisible ? showScale : hideScale})
|
|
4652
|
+
rotate(${isVisible ? showRotate : hideRotate}deg)
|
|
4653
|
+
translate(${isVisible ? showTranslateX : hideTranslateX}px, ${isVisible ? showTranslateY : hideTranslateY}px)
|
|
4654
|
+
`,
|
|
4655
|
+
opacity: isVisible ? showOpacity : hideOpacity,
|
|
4656
|
+
transition: `all ${duration}ms ${easing2}`,
|
|
4657
|
+
willChange: "transform, opacity"
|
|
4658
|
+
};
|
|
4659
|
+
return {
|
|
4660
|
+
ref,
|
|
4661
|
+
isVisible,
|
|
4662
|
+
isAnimating,
|
|
4663
|
+
style,
|
|
4664
|
+
progress,
|
|
4665
|
+
start,
|
|
4666
|
+
stop,
|
|
4667
|
+
reset,
|
|
4668
|
+
pause,
|
|
4669
|
+
resume
|
|
4670
|
+
};
|
|
4671
|
+
}
|
|
4672
|
+
function useCardList(options = {}) {
|
|
4673
|
+
const {
|
|
4674
|
+
duration = 500,
|
|
4675
|
+
easing: easing2 = "ease-out",
|
|
4676
|
+
staggerDelay = 100,
|
|
4677
|
+
cardScale = 1,
|
|
4678
|
+
cardOpacity = 1,
|
|
4679
|
+
cardRotate = 0,
|
|
4680
|
+
cardTranslateY = 0,
|
|
4681
|
+
cardTranslateX = 0,
|
|
4682
|
+
initialScale = 0.8,
|
|
4683
|
+
initialOpacity = 0,
|
|
4684
|
+
initialRotate = 0,
|
|
4685
|
+
initialTranslateY = 30,
|
|
4686
|
+
initialTranslateX = 0,
|
|
4687
|
+
gridColumns = 3,
|
|
4688
|
+
gridGap = 20,
|
|
4689
|
+
onComplete,
|
|
4690
|
+
onStart,
|
|
4691
|
+
onStop,
|
|
4692
|
+
onReset
|
|
4693
|
+
} = options;
|
|
4694
|
+
const ref = useRef(null);
|
|
4695
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
4696
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
4697
|
+
const [progress, setProgress] = useState(0);
|
|
4698
|
+
const [cardCount, setCardCount] = useState(0);
|
|
4699
|
+
const startRef = useRef(() => {
|
|
4700
|
+
});
|
|
4701
|
+
useEffect(() => {
|
|
4702
|
+
if (ref.current) {
|
|
4703
|
+
const cards = ref.current.querySelectorAll("[data-card]");
|
|
4704
|
+
setCardCount(cards.length);
|
|
4705
|
+
}
|
|
4706
|
+
}, []);
|
|
4707
|
+
const cardStyles = Array.from({ length: cardCount }, (_, index) => {
|
|
4708
|
+
const delay = isVisible ? index * staggerDelay : 0;
|
|
4709
|
+
const isCardVisible = isVisible && delay <= progress * (cardCount * staggerDelay);
|
|
4710
|
+
return {
|
|
4711
|
+
transform: `
|
|
4712
|
+
scale(${isCardVisible ? cardScale : initialScale})
|
|
4713
|
+
rotate(${isCardVisible ? cardRotate : initialRotate}deg)
|
|
4714
|
+
translate(${isCardVisible ? cardTranslateX : initialTranslateX}px, ${isCardVisible ? cardTranslateY : initialTranslateY}px)
|
|
4715
|
+
`,
|
|
4716
|
+
opacity: isCardVisible ? cardOpacity : initialOpacity,
|
|
4717
|
+
transition: `all ${duration}ms ${easing2} ${delay}ms`,
|
|
4718
|
+
willChange: "transform, opacity"
|
|
4719
|
+
};
|
|
4720
|
+
});
|
|
4721
|
+
const start = useCallback(() => {
|
|
4722
|
+
if (isAnimating) return;
|
|
4723
|
+
setIsAnimating(true);
|
|
4724
|
+
setProgress(0);
|
|
4725
|
+
onStart?.();
|
|
4726
|
+
const totalDuration = cardCount * staggerDelay + duration;
|
|
4727
|
+
const interval = setInterval(() => {
|
|
4728
|
+
setProgress((prev) => {
|
|
4729
|
+
const newProgress = prev + 0.1;
|
|
4730
|
+
if (newProgress >= 1) {
|
|
4731
|
+
setIsVisible(true);
|
|
4732
|
+
setIsAnimating(false);
|
|
4733
|
+
onComplete?.();
|
|
4734
|
+
clearInterval(interval);
|
|
4735
|
+
return 1;
|
|
4736
|
+
}
|
|
4737
|
+
return newProgress;
|
|
4738
|
+
});
|
|
4739
|
+
}, totalDuration / 10);
|
|
4740
|
+
}, [isAnimating, cardCount, staggerDelay, duration, onStart, onComplete]);
|
|
4741
|
+
startRef.current = start;
|
|
4742
|
+
const stop = useCallback(() => {
|
|
4743
|
+
setIsAnimating(false);
|
|
4744
|
+
onStop?.();
|
|
4745
|
+
}, [onStop]);
|
|
4746
|
+
const reset = useCallback(() => {
|
|
4747
|
+
setIsVisible(false);
|
|
4748
|
+
setIsAnimating(false);
|
|
4749
|
+
setProgress(0);
|
|
4750
|
+
onReset?.();
|
|
4751
|
+
}, [onReset]);
|
|
4752
|
+
const pause = useCallback(() => {
|
|
4753
|
+
setIsAnimating(false);
|
|
4754
|
+
}, []);
|
|
4755
|
+
const resume = useCallback(() => {
|
|
4756
|
+
if (!isVisible && !isAnimating) {
|
|
4757
|
+
start();
|
|
4758
|
+
}
|
|
4759
|
+
}, [isVisible, isAnimating, start]);
|
|
4760
|
+
useEffect(() => {
|
|
4761
|
+
if (!ref.current) return;
|
|
4762
|
+
return observeElement(
|
|
4763
|
+
ref.current,
|
|
4764
|
+
(entry) => {
|
|
4765
|
+
if (entry.isIntersecting) startRef.current();
|
|
4766
|
+
},
|
|
4767
|
+
{ threshold: 0.1 }
|
|
4768
|
+
);
|
|
4769
|
+
}, []);
|
|
4770
|
+
const gridStyle = {
|
|
4771
|
+
display: "grid",
|
|
4772
|
+
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
|
|
4773
|
+
gap: `${gridGap}px`,
|
|
4774
|
+
width: "100%"
|
|
4775
|
+
};
|
|
4776
|
+
return {
|
|
4777
|
+
ref,
|
|
4778
|
+
isVisible,
|
|
4779
|
+
isAnimating,
|
|
4780
|
+
style: gridStyle,
|
|
4781
|
+
progress,
|
|
4782
|
+
start,
|
|
4783
|
+
stop,
|
|
4784
|
+
reset,
|
|
4785
|
+
pause,
|
|
4786
|
+
resume,
|
|
4787
|
+
cardStyles,
|
|
4788
|
+
staggerDelay,
|
|
4789
|
+
gridColumns,
|
|
4790
|
+
gridGap
|
|
4791
|
+
};
|
|
4792
|
+
}
|
|
4793
|
+
function useLoadingSpinner(options = {}) {
|
|
4794
|
+
const {
|
|
4795
|
+
duration = 1e3,
|
|
4796
|
+
easing: easing2 = "linear",
|
|
4797
|
+
type = "rotate",
|
|
4798
|
+
rotationSpeed = 1,
|
|
4799
|
+
pulseSpeed = 1,
|
|
4800
|
+
bounceHeight = 20,
|
|
4801
|
+
waveCount = 3,
|
|
4802
|
+
dotCount = 3,
|
|
4803
|
+
barCount = 4,
|
|
4804
|
+
color = "#3b82f6",
|
|
4805
|
+
backgroundColor = "transparent",
|
|
4806
|
+
size = 40,
|
|
4807
|
+
thickness = 4,
|
|
4808
|
+
autoStart = true,
|
|
4809
|
+
infinite = true,
|
|
4810
|
+
onComplete,
|
|
4811
|
+
onStart,
|
|
4812
|
+
onStop,
|
|
4813
|
+
onReset
|
|
4814
|
+
} = options;
|
|
4815
|
+
const ref = useRef(null);
|
|
4816
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
4817
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
4818
|
+
const [progress, setProgress] = useState(0);
|
|
4819
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
4820
|
+
const [rotationAngle, setRotationAngle] = useState(0);
|
|
4821
|
+
const [pulseScale, setPulseScale] = useState(1);
|
|
4822
|
+
const [bounceOffset, setBounceOffset] = useState(0);
|
|
4823
|
+
const [waveProgress, setWaveProgress] = useState(0);
|
|
4824
|
+
const [dotProgress, setDotProgress] = useState(0);
|
|
4825
|
+
const [barProgress, setBarProgress] = useState(0);
|
|
4826
|
+
const animationRef = useRef(null);
|
|
4827
|
+
const startTimeRef = useRef(0);
|
|
4828
|
+
const runAnimation = useCallback(() => {
|
|
4829
|
+
if (!isAnimating) return;
|
|
4830
|
+
const animate = (currentTime) => {
|
|
4831
|
+
if (!isAnimating) return;
|
|
4832
|
+
if (!startTimeRef.current) {
|
|
4833
|
+
startTimeRef.current = currentTime;
|
|
4834
|
+
}
|
|
4835
|
+
const elapsed = currentTime - startTimeRef.current;
|
|
4836
|
+
const currentProgress = elapsed / duration % 1;
|
|
4837
|
+
setProgress(currentProgress);
|
|
4838
|
+
switch (type) {
|
|
4839
|
+
case "rotate":
|
|
4840
|
+
setRotationAngle(currentProgress * 360 * rotationSpeed);
|
|
4841
|
+
break;
|
|
4842
|
+
case "pulse":
|
|
4843
|
+
setPulseScale(0.8 + 0.4 * Math.sin(currentProgress * Math.PI * 2 * pulseSpeed));
|
|
4844
|
+
break;
|
|
4845
|
+
case "bounce":
|
|
4846
|
+
setBounceOffset(bounceHeight * Math.sin(currentProgress * Math.PI * 2));
|
|
4847
|
+
break;
|
|
4848
|
+
case "wave":
|
|
4849
|
+
setWaveProgress(currentProgress);
|
|
4850
|
+
break;
|
|
4851
|
+
case "dots":
|
|
4852
|
+
setDotProgress(currentProgress);
|
|
4853
|
+
break;
|
|
4854
|
+
case "bars":
|
|
4855
|
+
setBarProgress(currentProgress);
|
|
4856
|
+
break;
|
|
4857
|
+
}
|
|
4858
|
+
if (infinite || currentProgress < 1) {
|
|
4859
|
+
animationRef.current = requestAnimationFrame(animate);
|
|
4860
|
+
} else {
|
|
4861
|
+
setIsAnimating(false);
|
|
4862
|
+
onComplete?.();
|
|
4863
|
+
}
|
|
4864
|
+
};
|
|
4865
|
+
animationRef.current = requestAnimationFrame(animate);
|
|
4866
|
+
}, [isAnimating, duration, type, rotationSpeed, pulseSpeed, bounceHeight, infinite, onComplete]);
|
|
4867
|
+
const startLoading = useCallback(() => {
|
|
4868
|
+
if (isLoading) return;
|
|
4869
|
+
setIsLoading(true);
|
|
4870
|
+
setIsAnimating(true);
|
|
4871
|
+
setProgress(0);
|
|
4872
|
+
startTimeRef.current = 0;
|
|
4873
|
+
onStart?.();
|
|
4874
|
+
runAnimation();
|
|
4875
|
+
}, [isLoading, onStart, runAnimation]);
|
|
4876
|
+
const stopLoading = useCallback(() => {
|
|
4877
|
+
setIsLoading(false);
|
|
4878
|
+
setIsAnimating(false);
|
|
4879
|
+
if (animationRef.current) {
|
|
4880
|
+
cancelAnimationFrame(animationRef.current);
|
|
4881
|
+
animationRef.current = null;
|
|
4882
|
+
}
|
|
4883
|
+
onStop?.();
|
|
4884
|
+
}, [onStop]);
|
|
4885
|
+
const setLoadingState = useCallback((loading) => {
|
|
4886
|
+
if (loading) {
|
|
4887
|
+
startLoading();
|
|
4888
|
+
} else {
|
|
4889
|
+
stopLoading();
|
|
4890
|
+
}
|
|
4891
|
+
}, [startLoading, stopLoading]);
|
|
4892
|
+
const start = useCallback(() => {
|
|
4893
|
+
if (!isVisible) {
|
|
4894
|
+
setIsVisible(true);
|
|
4895
|
+
startLoading();
|
|
4896
|
+
}
|
|
4897
|
+
}, [isVisible, startLoading]);
|
|
4898
|
+
const stop = useCallback(() => {
|
|
4899
|
+
stopLoading();
|
|
4900
|
+
}, [stopLoading]);
|
|
4901
|
+
const reset = useCallback(() => {
|
|
4902
|
+
setIsVisible(false);
|
|
4903
|
+
setIsAnimating(false);
|
|
4904
|
+
setProgress(0);
|
|
4905
|
+
setIsLoading(false);
|
|
4906
|
+
setRotationAngle(0);
|
|
4907
|
+
setPulseScale(1);
|
|
4908
|
+
setBounceOffset(0);
|
|
4909
|
+
setWaveProgress(0);
|
|
4910
|
+
setDotProgress(0);
|
|
4911
|
+
setBarProgress(0);
|
|
4912
|
+
startTimeRef.current = 0;
|
|
4913
|
+
if (animationRef.current) {
|
|
4914
|
+
cancelAnimationFrame(animationRef.current);
|
|
4915
|
+
animationRef.current = null;
|
|
4916
|
+
}
|
|
4917
|
+
onReset?.();
|
|
4918
|
+
}, [onReset]);
|
|
4919
|
+
const pause = useCallback(() => {
|
|
4920
|
+
setIsAnimating(false);
|
|
4921
|
+
if (animationRef.current) {
|
|
4922
|
+
cancelAnimationFrame(animationRef.current);
|
|
4923
|
+
animationRef.current = null;
|
|
4924
|
+
}
|
|
4925
|
+
}, []);
|
|
4926
|
+
const resume = useCallback(() => {
|
|
4927
|
+
if (isVisible && !isAnimating) {
|
|
4928
|
+
setIsAnimating(true);
|
|
4929
|
+
runAnimation();
|
|
4930
|
+
}
|
|
4931
|
+
}, [isVisible, isAnimating, runAnimation]);
|
|
4932
|
+
useEffect(() => {
|
|
4933
|
+
if (autoStart) {
|
|
4934
|
+
startLoading();
|
|
4935
|
+
}
|
|
4936
|
+
}, [autoStart, startLoading]);
|
|
4937
|
+
useEffect(() => {
|
|
4938
|
+
return () => {
|
|
4939
|
+
if (animationRef.current) {
|
|
4940
|
+
cancelAnimationFrame(animationRef.current);
|
|
4941
|
+
}
|
|
4942
|
+
};
|
|
4943
|
+
}, []);
|
|
4944
|
+
const getSpinnerStyle = () => {
|
|
4945
|
+
const baseStyle = {
|
|
4946
|
+
width: size,
|
|
4947
|
+
height: size,
|
|
4948
|
+
backgroundColor: "transparent",
|
|
4949
|
+
position: "relative",
|
|
4950
|
+
display: "inline-block"
|
|
4951
|
+
};
|
|
4952
|
+
switch (type) {
|
|
4953
|
+
case "rotate":
|
|
4954
|
+
return {
|
|
4955
|
+
...baseStyle,
|
|
4956
|
+
border: `${thickness}px solid ${backgroundColor}`,
|
|
4957
|
+
borderTop: `${thickness}px solid ${color}`,
|
|
4958
|
+
borderRadius: "50%",
|
|
4959
|
+
transform: `rotate(${rotationAngle}deg)`,
|
|
4960
|
+
transition: `transform ${duration}ms ${easing2}`
|
|
4961
|
+
};
|
|
4962
|
+
case "pulse":
|
|
4963
|
+
return {
|
|
4964
|
+
...baseStyle,
|
|
4965
|
+
backgroundColor: color,
|
|
4966
|
+
borderRadius: "50%",
|
|
4967
|
+
transform: `scale(${pulseScale})`,
|
|
4968
|
+
transition: `transform ${duration}ms ${easing2}`
|
|
4969
|
+
};
|
|
4970
|
+
case "bounce":
|
|
4971
|
+
return {
|
|
4972
|
+
...baseStyle,
|
|
4973
|
+
backgroundColor: color,
|
|
4974
|
+
borderRadius: "50%",
|
|
4975
|
+
transform: `translateY(${bounceOffset}px)`,
|
|
4976
|
+
transition: `transform ${duration}ms ${easing2}`
|
|
4977
|
+
};
|
|
4978
|
+
case "wave":
|
|
4979
|
+
return {
|
|
4980
|
+
...baseStyle,
|
|
4981
|
+
display: "flex",
|
|
4982
|
+
alignItems: "center",
|
|
4983
|
+
justifyContent: "space-between"
|
|
4984
|
+
};
|
|
4985
|
+
case "dots":
|
|
4986
|
+
return {
|
|
4987
|
+
...baseStyle,
|
|
4988
|
+
display: "flex",
|
|
4989
|
+
alignItems: "center",
|
|
4990
|
+
justifyContent: "space-between"
|
|
4991
|
+
};
|
|
4992
|
+
case "bars":
|
|
4993
|
+
return {
|
|
4994
|
+
...baseStyle,
|
|
4995
|
+
display: "flex",
|
|
4996
|
+
alignItems: "center",
|
|
4997
|
+
justifyContent: "space-between"
|
|
4998
|
+
};
|
|
4999
|
+
default:
|
|
5000
|
+
return baseStyle;
|
|
5001
|
+
}
|
|
5002
|
+
};
|
|
5003
|
+
const style = getSpinnerStyle();
|
|
5004
|
+
return {
|
|
5005
|
+
ref,
|
|
5006
|
+
isVisible,
|
|
5007
|
+
isAnimating,
|
|
5008
|
+
style,
|
|
5009
|
+
progress,
|
|
5010
|
+
start,
|
|
5011
|
+
stop,
|
|
5012
|
+
reset,
|
|
5013
|
+
pause,
|
|
5014
|
+
resume,
|
|
5015
|
+
isLoading,
|
|
5016
|
+
spinnerType: type,
|
|
5017
|
+
rotationAngle,
|
|
5018
|
+
pulseScale,
|
|
5019
|
+
bounceOffset,
|
|
5020
|
+
waveProgress,
|
|
5021
|
+
dotProgress,
|
|
5022
|
+
barProgress,
|
|
5023
|
+
startLoading,
|
|
5024
|
+
stopLoading,
|
|
5025
|
+
setLoadingState
|
|
5026
|
+
};
|
|
5027
|
+
}
|
|
5028
|
+
function useNavigation(options = {}) {
|
|
5029
|
+
const {
|
|
5030
|
+
duration = 300,
|
|
5031
|
+
easing: easing2 = "ease-out",
|
|
5032
|
+
type = "slide",
|
|
5033
|
+
slideDirection = "left",
|
|
5034
|
+
staggerDelay = 50,
|
|
5035
|
+
itemScale = 1,
|
|
5036
|
+
itemOpacity = 1,
|
|
5037
|
+
itemRotate = 0,
|
|
5038
|
+
itemTranslateY = 0,
|
|
5039
|
+
itemTranslateX = 0,
|
|
5040
|
+
initialScale = 0.8,
|
|
5041
|
+
initialOpacity = 0,
|
|
5042
|
+
initialRotate = -10,
|
|
5043
|
+
initialTranslateY = 20,
|
|
5044
|
+
initialTranslateX = 0,
|
|
5045
|
+
activeScale = 1.05,
|
|
5046
|
+
activeOpacity = 1,
|
|
5047
|
+
activeRotate = 0,
|
|
5048
|
+
activeTranslateY = 0,
|
|
5049
|
+
activeTranslateX = 0,
|
|
5050
|
+
hoverScale = 1.1,
|
|
5051
|
+
hoverOpacity = 1,
|
|
5052
|
+
hoverRotate = 5,
|
|
5053
|
+
hoverTranslateY = -5,
|
|
5054
|
+
hoverTranslateX = 0,
|
|
5055
|
+
itemCount = 5,
|
|
5056
|
+
autoStart = false,
|
|
5057
|
+
onComplete,
|
|
5058
|
+
onStart,
|
|
5059
|
+
onStop,
|
|
5060
|
+
onReset
|
|
5061
|
+
} = options;
|
|
5062
|
+
const ref = useRef(null);
|
|
5063
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
5064
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
5065
|
+
const [progress, setProgress] = useState(0);
|
|
5066
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
5067
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
5068
|
+
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
5069
|
+
const openMenu = useCallback(() => {
|
|
5070
|
+
if (isOpen) return;
|
|
5071
|
+
setIsOpen(true);
|
|
5072
|
+
setIsAnimating(true);
|
|
5073
|
+
setProgress(0);
|
|
5074
|
+
onStart?.();
|
|
5075
|
+
const totalDuration = itemCount * staggerDelay + duration;
|
|
5076
|
+
const interval = setInterval(() => {
|
|
5077
|
+
setProgress((prev) => {
|
|
5078
|
+
const newProgress = prev + 0.1;
|
|
5079
|
+
if (newProgress >= 1) {
|
|
5080
|
+
setIsAnimating(false);
|
|
5081
|
+
onComplete?.();
|
|
5082
|
+
clearInterval(interval);
|
|
5083
|
+
return 1;
|
|
5084
|
+
}
|
|
5085
|
+
return newProgress;
|
|
5086
|
+
});
|
|
5087
|
+
}, totalDuration / 10);
|
|
5088
|
+
}, [isOpen, itemCount, staggerDelay, duration, onStart, onComplete]);
|
|
5089
|
+
const closeMenu = useCallback(() => {
|
|
5090
|
+
if (!isOpen) return;
|
|
5091
|
+
setIsOpen(false);
|
|
5092
|
+
setIsAnimating(true);
|
|
5093
|
+
setProgress(1);
|
|
5094
|
+
setTimeout(() => {
|
|
5095
|
+
setIsAnimating(false);
|
|
5096
|
+
setProgress(0);
|
|
5097
|
+
}, duration);
|
|
5098
|
+
}, [isOpen, duration]);
|
|
5099
|
+
const toggleMenu = useCallback(() => {
|
|
5100
|
+
if (isOpen) {
|
|
5101
|
+
closeMenu();
|
|
5102
|
+
} else {
|
|
5103
|
+
openMenu();
|
|
5104
|
+
}
|
|
5105
|
+
}, [isOpen, openMenu, closeMenu]);
|
|
5106
|
+
const setActiveItem = useCallback((index) => {
|
|
5107
|
+
if (index >= 0 && index < itemCount) {
|
|
5108
|
+
setActiveIndex(index);
|
|
5109
|
+
}
|
|
5110
|
+
}, [itemCount]);
|
|
5111
|
+
const goToNext = useCallback(() => {
|
|
5112
|
+
setActiveItem((activeIndex + 1) % itemCount);
|
|
5113
|
+
}, [activeIndex, itemCount, setActiveItem]);
|
|
5114
|
+
const goToPrevious = useCallback(() => {
|
|
5115
|
+
setActiveItem(activeIndex === 0 ? itemCount - 1 : activeIndex - 1);
|
|
5116
|
+
}, [activeIndex, itemCount, setActiveItem]);
|
|
5117
|
+
const start = useCallback(() => {
|
|
5118
|
+
if (!isVisible) {
|
|
5119
|
+
setIsVisible(true);
|
|
5120
|
+
openMenu();
|
|
5121
|
+
}
|
|
5122
|
+
}, [isVisible, openMenu]);
|
|
5123
|
+
const stop = useCallback(() => {
|
|
5124
|
+
setIsAnimating(false);
|
|
5125
|
+
onStop?.();
|
|
5126
|
+
}, [onStop]);
|
|
5127
|
+
const reset = useCallback(() => {
|
|
5128
|
+
setIsVisible(false);
|
|
5129
|
+
setIsAnimating(false);
|
|
5130
|
+
setProgress(0);
|
|
5131
|
+
setIsOpen(false);
|
|
5132
|
+
setActiveIndex(0);
|
|
5133
|
+
setHoveredIndex(null);
|
|
5134
|
+
onReset?.();
|
|
5135
|
+
}, [onReset]);
|
|
5136
|
+
const pause = useCallback(() => {
|
|
5137
|
+
setIsAnimating(false);
|
|
5138
|
+
}, []);
|
|
5139
|
+
const resume = useCallback(() => {
|
|
5140
|
+
if (isVisible) {
|
|
5141
|
+
setIsAnimating(true);
|
|
5142
|
+
}
|
|
5143
|
+
}, [isVisible]);
|
|
5144
|
+
useEffect(() => {
|
|
5145
|
+
if (autoStart) {
|
|
5146
|
+
start();
|
|
5147
|
+
}
|
|
5148
|
+
}, [autoStart, start]);
|
|
5149
|
+
const itemStyles = Array.from({ length: itemCount }, (_, index) => {
|
|
5150
|
+
const delay = isOpen ? index * staggerDelay : 0;
|
|
5151
|
+
const isItemVisible = isOpen && delay <= progress * (itemCount * staggerDelay);
|
|
5152
|
+
const isActive = index === activeIndex;
|
|
5153
|
+
const isHovered = index === hoveredIndex;
|
|
5154
|
+
let scale = initialScale;
|
|
5155
|
+
let opacity = initialOpacity;
|
|
5156
|
+
let rotate = initialRotate;
|
|
5157
|
+
let translateY = initialTranslateY;
|
|
5158
|
+
let translateX = initialTranslateX;
|
|
5159
|
+
if (isItemVisible) {
|
|
5160
|
+
if (isHovered) {
|
|
5161
|
+
scale = hoverScale;
|
|
5162
|
+
opacity = hoverOpacity;
|
|
5163
|
+
rotate = hoverRotate;
|
|
5164
|
+
translateY = hoverTranslateY;
|
|
5165
|
+
translateX = hoverTranslateX;
|
|
5166
|
+
} else if (isActive) {
|
|
5167
|
+
scale = activeScale;
|
|
5168
|
+
opacity = activeOpacity;
|
|
5169
|
+
rotate = activeRotate;
|
|
5170
|
+
translateY = activeTranslateY;
|
|
5171
|
+
translateX = activeTranslateX;
|
|
5172
|
+
} else {
|
|
5173
|
+
scale = itemScale;
|
|
5174
|
+
opacity = itemOpacity;
|
|
5175
|
+
rotate = itemRotate;
|
|
5176
|
+
translateY = itemTranslateY;
|
|
5177
|
+
translateX = itemTranslateX;
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
return {
|
|
5181
|
+
transform: `
|
|
5182
|
+
scale(${scale})
|
|
5183
|
+
rotate(${rotate}deg)
|
|
5184
|
+
translate(${translateX}px, ${translateY}px)
|
|
5185
|
+
`,
|
|
5186
|
+
opacity,
|
|
5187
|
+
transition: `all ${duration}ms ${easing2} ${delay}ms`,
|
|
5188
|
+
willChange: "transform, opacity",
|
|
5189
|
+
cursor: "pointer"
|
|
5190
|
+
};
|
|
5191
|
+
});
|
|
5192
|
+
const getNavigationStyle = () => {
|
|
5193
|
+
const baseStyle = {
|
|
5194
|
+
transition: `all ${duration}ms ${easing2}`,
|
|
5195
|
+
willChange: "transform, opacity"
|
|
5196
|
+
};
|
|
5197
|
+
switch (type) {
|
|
5198
|
+
case "slide":
|
|
5199
|
+
if (slideDirection === "left") {
|
|
5200
|
+
baseStyle.transform = `translateX(${isOpen ? 0 : -100}%)`;
|
|
5201
|
+
} else if (slideDirection === "right") {
|
|
5202
|
+
baseStyle.transform = `translateX(${isOpen ? 0 : 100}%)`;
|
|
5203
|
+
} else if (slideDirection === "up") {
|
|
5204
|
+
baseStyle.transform = `translateY(${isOpen ? 0 : -100}%)`;
|
|
5205
|
+
} else if (slideDirection === "down") {
|
|
5206
|
+
baseStyle.transform = `translateY(${isOpen ? 0 : 100}%)`;
|
|
5207
|
+
}
|
|
5208
|
+
break;
|
|
5209
|
+
case "fade":
|
|
5210
|
+
baseStyle.opacity = isOpen ? 1 : 0;
|
|
5211
|
+
break;
|
|
5212
|
+
case "scale":
|
|
5213
|
+
baseStyle.transform = `scale(${isOpen ? 1 : 0})`;
|
|
5214
|
+
break;
|
|
5215
|
+
case "rotate":
|
|
5216
|
+
baseStyle.transform = `rotate(${isOpen ? 0 : 180}deg)`;
|
|
5217
|
+
break;
|
|
5218
|
+
}
|
|
5219
|
+
return baseStyle;
|
|
5220
|
+
};
|
|
5221
|
+
const style = getNavigationStyle();
|
|
4126
5222
|
return {
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
deltaY,
|
|
4133
|
-
distance,
|
|
4134
|
-
velocity,
|
|
5223
|
+
ref,
|
|
5224
|
+
isVisible,
|
|
5225
|
+
isAnimating,
|
|
5226
|
+
style,
|
|
5227
|
+
progress,
|
|
4135
5228
|
start,
|
|
4136
5229
|
stop,
|
|
4137
5230
|
reset,
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
5231
|
+
pause,
|
|
5232
|
+
resume,
|
|
5233
|
+
isOpen,
|
|
5234
|
+
activeIndex,
|
|
5235
|
+
itemStyles,
|
|
5236
|
+
openMenu,
|
|
5237
|
+
closeMenu,
|
|
5238
|
+
toggleMenu,
|
|
5239
|
+
setActiveItem,
|
|
5240
|
+
goToNext,
|
|
5241
|
+
goToPrevious
|
|
4144
5242
|
};
|
|
4145
5243
|
}
|
|
4146
|
-
function
|
|
5244
|
+
function useSkeleton(options = {}) {
|
|
4147
5245
|
const {
|
|
4148
|
-
|
|
4149
|
-
duration =
|
|
4150
|
-
easing: easing2 = "ease-out",
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
5246
|
+
delay = 0,
|
|
5247
|
+
duration = 1500,
|
|
5248
|
+
easing: easing2 = "ease-in-out",
|
|
5249
|
+
autoStart = true,
|
|
5250
|
+
backgroundColor = "#f0f0f0",
|
|
5251
|
+
highlightColor = "#e0e0e0",
|
|
5252
|
+
motionSpeed = 1500,
|
|
5253
|
+
height = 20,
|
|
5254
|
+
width = "100%",
|
|
5255
|
+
borderRadius = 4,
|
|
5256
|
+
wave = true,
|
|
5257
|
+
pulse: pulse2 = false,
|
|
5258
|
+
onComplete,
|
|
5259
|
+
onStart,
|
|
5260
|
+
onStop,
|
|
5261
|
+
onReset
|
|
4155
5262
|
} = options;
|
|
4156
|
-
const
|
|
4157
|
-
const [
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
5263
|
+
const ref = useRef(null);
|
|
5264
|
+
const [isVisible, setIsVisible] = useState(autoStart);
|
|
5265
|
+
const [isAnimating, setIsAnimating] = useState(autoStart);
|
|
5266
|
+
const [progress, setProgress] = useState(0);
|
|
5267
|
+
const motionRef = useRef(null);
|
|
5268
|
+
const startTimeRef = useRef(null);
|
|
5269
|
+
const start = useCallback(() => {
|
|
5270
|
+
if (isAnimating) return;
|
|
5271
|
+
setIsVisible(true);
|
|
5272
|
+
setIsAnimating(true);
|
|
5273
|
+
setProgress(0);
|
|
5274
|
+
onStart?.();
|
|
5275
|
+
setTimeout(() => {
|
|
5276
|
+
startTimeRef.current = Date.now();
|
|
5277
|
+
const animate = () => {
|
|
5278
|
+
if (!startTimeRef.current) return;
|
|
5279
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
5280
|
+
const newProgress = Math.min(elapsed / duration, 1);
|
|
5281
|
+
setProgress(newProgress);
|
|
5282
|
+
if (newProgress < 1) {
|
|
5283
|
+
motionRef.current = requestAnimationFrame(animate);
|
|
5284
|
+
} else {
|
|
5285
|
+
setIsAnimating(false);
|
|
5286
|
+
onComplete?.();
|
|
5287
|
+
}
|
|
5288
|
+
};
|
|
5289
|
+
motionRef.current = requestAnimationFrame(animate);
|
|
5290
|
+
}, delay);
|
|
5291
|
+
}, [delay, duration, isAnimating, onStart, onComplete]);
|
|
5292
|
+
const stop = useCallback(() => {
|
|
5293
|
+
if (motionRef.current) {
|
|
5294
|
+
cancelAnimationFrame(motionRef.current);
|
|
5295
|
+
motionRef.current = null;
|
|
4189
5296
|
}
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
deltaY
|
|
4213
|
-
}));
|
|
4214
|
-
}, [enabled]);
|
|
4215
|
-
const handleMouseUp = useCallback(() => {
|
|
4216
|
-
if (!enabled) return;
|
|
4217
|
-
isDragging.current = false;
|
|
4218
|
-
setGestureState((prev) => ({ ...prev, isActive: false }));
|
|
4219
|
-
onGestureEnd?.();
|
|
4220
|
-
}, [enabled, onGestureEnd]);
|
|
4221
|
-
const handleMouseEnter = useCallback(() => {
|
|
4222
|
-
if (!enabled || gestureType !== "hover") return;
|
|
4223
|
-
setGestureState((prev) => ({ ...prev, isActive: true }));
|
|
4224
|
-
onGestureStart?.();
|
|
4225
|
-
}, [enabled, gestureType, onGestureStart]);
|
|
4226
|
-
const handleMouseLeave = useCallback(() => {
|
|
4227
|
-
if (!enabled || gestureType !== "hover") return;
|
|
4228
|
-
setGestureState((prev) => ({ ...prev, isActive: false }));
|
|
4229
|
-
onGestureEnd?.();
|
|
4230
|
-
}, [enabled, gestureType, onGestureEnd]);
|
|
4231
|
-
const handleTouchStart = useCallback((e) => {
|
|
4232
|
-
if (!enabled) return;
|
|
4233
|
-
const touch = e.touches[0];
|
|
4234
|
-
startPoint.current = { x: touch.clientX, y: touch.clientY };
|
|
4235
|
-
setGestureState((prev) => ({ ...prev, isActive: true }));
|
|
4236
|
-
onGestureStart?.();
|
|
4237
|
-
}, [enabled, onGestureStart]);
|
|
4238
|
-
const handleTouchMove = useCallback((e) => {
|
|
4239
|
-
if (!enabled) return;
|
|
4240
|
-
const touch = e.touches[0];
|
|
4241
|
-
const deltaX = touch.clientX - startPoint.current.x;
|
|
4242
|
-
const deltaY = touch.clientY - startPoint.current.y;
|
|
4243
|
-
setGestureState((prev) => ({
|
|
4244
|
-
...prev,
|
|
4245
|
-
x: touch.clientX,
|
|
4246
|
-
y: touch.clientY,
|
|
4247
|
-
deltaX,
|
|
4248
|
-
deltaY
|
|
4249
|
-
}));
|
|
4250
|
-
}, [enabled]);
|
|
4251
|
-
const handleTouchEnd = useCallback(() => {
|
|
4252
|
-
if (!enabled) return;
|
|
4253
|
-
setGestureState((prev) => ({ ...prev, isActive: false }));
|
|
4254
|
-
onGestureEnd?.();
|
|
4255
|
-
}, [enabled, onGestureEnd]);
|
|
5297
|
+
startTimeRef.current = null;
|
|
5298
|
+
setIsAnimating(false);
|
|
5299
|
+
onStop?.();
|
|
5300
|
+
}, [onStop]);
|
|
5301
|
+
const reset = useCallback(() => {
|
|
5302
|
+
stop();
|
|
5303
|
+
setIsVisible(false);
|
|
5304
|
+
setProgress(0);
|
|
5305
|
+
onReset?.();
|
|
5306
|
+
}, [stop, onReset]);
|
|
5307
|
+
const pause = useCallback(() => {
|
|
5308
|
+
if (motionRef.current) {
|
|
5309
|
+
cancelAnimationFrame(motionRef.current);
|
|
5310
|
+
motionRef.current = null;
|
|
5311
|
+
}
|
|
5312
|
+
setIsAnimating(false);
|
|
5313
|
+
}, []);
|
|
5314
|
+
const resume = useCallback(() => {
|
|
5315
|
+
if (!isAnimating && isVisible) {
|
|
5316
|
+
start();
|
|
5317
|
+
}
|
|
5318
|
+
}, [isAnimating, isVisible, start]);
|
|
4256
5319
|
useEffect(() => {
|
|
4257
|
-
if (
|
|
4258
|
-
|
|
4259
|
-
if (gestureType === "hover") {
|
|
4260
|
-
element.addEventListener("mouseenter", handleMouseEnter);
|
|
4261
|
-
element.addEventListener("mouseleave", handleMouseLeave);
|
|
4262
|
-
} else if (gestureType === "drag") {
|
|
4263
|
-
element.addEventListener("mousedown", handleMouseDown);
|
|
4264
|
-
document.addEventListener("mousemove", handleMouseMove);
|
|
4265
|
-
document.addEventListener("mouseup", handleMouseUp);
|
|
5320
|
+
if (autoStart) {
|
|
5321
|
+
start();
|
|
4266
5322
|
}
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
element.addEventListener("touchend", handleTouchEnd);
|
|
5323
|
+
}, [autoStart, start]);
|
|
5324
|
+
useEffect(() => {
|
|
4270
5325
|
return () => {
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
document.removeEventListener("mousemove", handleMouseMove);
|
|
4275
|
-
document.removeEventListener("mouseup", handleMouseUp);
|
|
4276
|
-
element.removeEventListener("touchstart", handleTouchStart);
|
|
4277
|
-
element.removeEventListener("touchmove", handleTouchMove);
|
|
4278
|
-
element.removeEventListener("touchend", handleTouchEnd);
|
|
5326
|
+
if (motionRef.current) {
|
|
5327
|
+
cancelAnimationFrame(motionRef.current);
|
|
5328
|
+
}
|
|
4279
5329
|
};
|
|
4280
|
-
}, [
|
|
5330
|
+
}, []);
|
|
5331
|
+
const getSkeletonStyle = () => {
|
|
5332
|
+
const baseStyle = {
|
|
5333
|
+
width: typeof width === "number" ? `${width}px` : width,
|
|
5334
|
+
height: `${height}px`,
|
|
5335
|
+
backgroundColor,
|
|
5336
|
+
borderRadius: `${borderRadius}px`,
|
|
5337
|
+
position: "relative",
|
|
5338
|
+
overflow: "hidden",
|
|
5339
|
+
opacity: isVisible ? 1 : 0,
|
|
5340
|
+
transition: `opacity ${duration}ms ${easing2}`
|
|
5341
|
+
};
|
|
5342
|
+
if (wave && isAnimating) {
|
|
5343
|
+
baseStyle.background = `linear-gradient(90deg, ${backgroundColor} 25%, ${highlightColor} 50%, ${backgroundColor} 75%)`;
|
|
5344
|
+
baseStyle.backgroundSize = "200% 100%";
|
|
5345
|
+
baseStyle.animation = `skeleton-wave ${motionSpeed}ms infinite linear`;
|
|
5346
|
+
} else if (pulse2 && isAnimating) {
|
|
5347
|
+
baseStyle.animation = `skeleton-pulse ${motionSpeed}ms infinite ease-in-out`;
|
|
5348
|
+
}
|
|
5349
|
+
return baseStyle;
|
|
5350
|
+
};
|
|
5351
|
+
const style = getSkeletonStyle();
|
|
4281
5352
|
useEffect(() => {
|
|
4282
|
-
|
|
4283
|
-
|
|
5353
|
+
const styleSheet = document.styleSheets[0] || document.createElement("style").sheet;
|
|
5354
|
+
if (styleSheet && !document.getElementById("skeleton-animations")) {
|
|
5355
|
+
const styleElement = document.createElement("style");
|
|
5356
|
+
styleElement.id = "skeleton-animations";
|
|
5357
|
+
styleElement.textContent = `
|
|
5358
|
+
@keyframes skeleton-wave {
|
|
5359
|
+
0% { background-position: 200% 0; }
|
|
5360
|
+
100% { background-position: -200% 0; }
|
|
5361
|
+
}
|
|
5362
|
+
|
|
5363
|
+
@keyframes skeleton-pulse {
|
|
5364
|
+
0%, 100% { opacity: 0.6; }
|
|
5365
|
+
50% { opacity: 1; }
|
|
5366
|
+
}
|
|
5367
|
+
`;
|
|
5368
|
+
document.head.appendChild(styleElement);
|
|
5369
|
+
}
|
|
5370
|
+
}, []);
|
|
4284
5371
|
return {
|
|
4285
|
-
ref
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
5372
|
+
ref,
|
|
5373
|
+
isVisible,
|
|
5374
|
+
isAnimating,
|
|
5375
|
+
style,
|
|
5376
|
+
progress,
|
|
5377
|
+
start,
|
|
5378
|
+
stop,
|
|
5379
|
+
reset,
|
|
5380
|
+
pause,
|
|
5381
|
+
resume
|
|
4289
5382
|
};
|
|
4290
5383
|
}
|
|
4291
5384
|
function useTypewriter(options) {
|
|
@@ -4440,7 +5533,7 @@ function useSmoothScroll(options = {}) {
|
|
|
4440
5533
|
wheelMultiplier = 1,
|
|
4441
5534
|
touchMultiplier = 2,
|
|
4442
5535
|
direction = "vertical",
|
|
4443
|
-
onScroll
|
|
5536
|
+
onScroll: onScroll2
|
|
4444
5537
|
} = options;
|
|
4445
5538
|
const [scroll, setScroll] = useState(0);
|
|
4446
5539
|
const [progress, setProgress] = useState(0);
|
|
@@ -4468,14 +5561,14 @@ function useSmoothScroll(options = {}) {
|
|
|
4468
5561
|
setScroll(currentRef.current);
|
|
4469
5562
|
const max = getMaxScroll();
|
|
4470
5563
|
setProgress(max > 0 ? currentRef.current / max : 0);
|
|
4471
|
-
|
|
5564
|
+
onScroll2?.(currentRef.current);
|
|
4472
5565
|
if (direction === "vertical") {
|
|
4473
5566
|
window.scrollTo(0, currentRef.current);
|
|
4474
5567
|
} else {
|
|
4475
5568
|
window.scrollTo(currentRef.current, 0);
|
|
4476
5569
|
}
|
|
4477
5570
|
rafRef.current = requestAnimationFrame(animate);
|
|
4478
|
-
}, [lerp, direction, getMaxScroll,
|
|
5571
|
+
}, [lerp, direction, getMaxScroll, onScroll2]);
|
|
4479
5572
|
const startAnimation = useCallback(() => {
|
|
4480
5573
|
if (isRunningRef.current) return;
|
|
4481
5574
|
isRunningRef.current = true;
|
|
@@ -4545,6 +5638,8 @@ function useElementProgress(options = {}) {
|
|
|
4545
5638
|
const ref = useRef(null);
|
|
4546
5639
|
const [progress, setProgress] = useState(0);
|
|
4547
5640
|
const [isInView, setIsInView] = useState(false);
|
|
5641
|
+
const progressRef = useRef(0);
|
|
5642
|
+
const isInViewRef = useRef(false);
|
|
4548
5643
|
useEffect(() => {
|
|
4549
5644
|
const el = ref.current;
|
|
4550
5645
|
if (!el || typeof window === "undefined") return;
|
|
@@ -4558,16 +5653,18 @@ function useElementProgress(options = {}) {
|
|
|
4558
5653
|
const range = trackStart - trackEnd;
|
|
4559
5654
|
const raw = range > 0 ? (trackStart - elementTop) / range : 0;
|
|
4560
5655
|
const clamped = clamp ? Math.max(0, Math.min(1, raw)) : raw;
|
|
4561
|
-
|
|
4562
|
-
|
|
5656
|
+
if (Math.abs(clamped - progressRef.current) > 5e-3) {
|
|
5657
|
+
progressRef.current = clamped;
|
|
5658
|
+
setProgress(clamped);
|
|
5659
|
+
}
|
|
5660
|
+
const nowInView = elementBottom > 0 && elementTop < vh;
|
|
5661
|
+
if (nowInView !== isInViewRef.current) {
|
|
5662
|
+
isInViewRef.current = nowInView;
|
|
5663
|
+
setIsInView(nowInView);
|
|
5664
|
+
}
|
|
4563
5665
|
};
|
|
4564
5666
|
calculate();
|
|
4565
|
-
|
|
4566
|
-
window.addEventListener("resize", calculate, { passive: true });
|
|
4567
|
-
return () => {
|
|
4568
|
-
window.removeEventListener("scroll", calculate);
|
|
4569
|
-
window.removeEventListener("resize", calculate);
|
|
4570
|
-
};
|
|
5667
|
+
return subscribeScroll(calculate);
|
|
4571
5668
|
}, [start, end, clamp]);
|
|
4572
5669
|
return { ref, progress, isInView };
|
|
4573
5670
|
}
|
|
@@ -4618,16 +5715,16 @@ function Motion({
|
|
|
4618
5715
|
}
|
|
4619
5716
|
);
|
|
4620
5717
|
}
|
|
4621
|
-
function getInitialTransform(motionType) {
|
|
5718
|
+
function getInitialTransform(motionType, slideDistance, scaleFrom) {
|
|
4622
5719
|
switch (motionType) {
|
|
4623
5720
|
case "slideUp":
|
|
4624
|
-
return
|
|
5721
|
+
return `translateY(${slideDistance}px)`;
|
|
4625
5722
|
case "slideLeft":
|
|
4626
|
-
return
|
|
5723
|
+
return `translateX(-${slideDistance}px)`;
|
|
4627
5724
|
case "slideRight":
|
|
4628
|
-
return
|
|
5725
|
+
return `translateX(${slideDistance}px)`;
|
|
4629
5726
|
case "scaleIn":
|
|
4630
|
-
return
|
|
5727
|
+
return `scale(${scaleFrom})`;
|
|
4631
5728
|
case "bounceIn":
|
|
4632
5729
|
return "scale(0.75)";
|
|
4633
5730
|
case "fadeIn":
|
|
@@ -4636,36 +5733,33 @@ function getInitialTransform(motionType) {
|
|
|
4636
5733
|
}
|
|
4637
5734
|
}
|
|
4638
5735
|
function useStagger(options) {
|
|
5736
|
+
const profile = useMotionProfile();
|
|
4639
5737
|
const {
|
|
4640
5738
|
count,
|
|
4641
|
-
staggerDelay =
|
|
4642
|
-
baseDelay =
|
|
4643
|
-
duration =
|
|
5739
|
+
staggerDelay = profile.stagger.perItem,
|
|
5740
|
+
baseDelay = profile.stagger.baseDelay,
|
|
5741
|
+
duration = profile.base.duration,
|
|
4644
5742
|
motionType = "fadeIn",
|
|
4645
|
-
threshold =
|
|
4646
|
-
easing: easing2 =
|
|
5743
|
+
threshold = profile.base.threshold,
|
|
5744
|
+
easing: easing2 = profile.base.easing
|
|
4647
5745
|
} = options;
|
|
4648
5746
|
const containerRef = useRef(null);
|
|
4649
5747
|
const [isVisible, setIsVisible] = useState(false);
|
|
4650
5748
|
useEffect(() => {
|
|
4651
5749
|
if (!containerRef.current) return;
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
setIsVisible(true);
|
|
4657
|
-
observer.disconnect();
|
|
4658
|
-
}
|
|
4659
|
-
});
|
|
5750
|
+
return observeElement(
|
|
5751
|
+
containerRef.current,
|
|
5752
|
+
(entry) => {
|
|
5753
|
+
if (entry.isIntersecting) setIsVisible(true);
|
|
4660
5754
|
},
|
|
4661
|
-
{ threshold }
|
|
5755
|
+
{ threshold },
|
|
5756
|
+
true
|
|
5757
|
+
// once — 첫 intersection 후 자동 정리
|
|
4662
5758
|
);
|
|
4663
|
-
observer.observe(containerRef.current);
|
|
4664
|
-
return () => {
|
|
4665
|
-
observer.disconnect();
|
|
4666
|
-
};
|
|
4667
5759
|
}, [threshold]);
|
|
4668
|
-
const
|
|
5760
|
+
const slideDistance = profile.entrance.slide.distance;
|
|
5761
|
+
const scaleFrom = profile.entrance.scale.from;
|
|
5762
|
+
const initialTransform = useMemo(() => getInitialTransform(motionType, slideDistance, scaleFrom), [motionType, slideDistance, scaleFrom]);
|
|
4669
5763
|
const styles = useMemo(() => {
|
|
4670
5764
|
return Array.from({ length: count }, (_, i) => {
|
|
4671
5765
|
const itemDelay = baseDelay + i * staggerDelay;
|
|
@@ -4690,6 +5784,6 @@ function useStagger(options) {
|
|
|
4690
5784
|
};
|
|
4691
5785
|
}
|
|
4692
5786
|
|
|
4693
|
-
export { MOTION_PRESETS, Motion, MotionEngine,
|
|
5787
|
+
export { MOTION_PRESETS, Motion, 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, observeElement, resolveProfile, safeApplyEasing, transitionEffects, useBounceIn, useButtonEffect, useCardList, useClickToggle, useCustomCursor, useElementProgress, useFadeIn, useFocusToggle, useGesture, useGestureMotion, useGradient, useHoverMotion, useInView, useLoadingSpinner, useMagneticCursor, useMotionProfile, useMotionState, useMouse, useNavigation, usePageMotions, usePulse, useReducedMotion, useRepeat, useScaleIn, useScrollProgress, useScrollReveal, useScrollToggle, useSimplePageMotion, useSkeleton, useSlideDown, useSlideLeft, useSlideRight, useSlideUp, useSmartMotion, useSmoothScroll, useSpringMotion, useStagger, useToggleMotion, useTypewriter, useUnifiedMotion, useVisibilityToggle, useWindowSize };
|
|
4694
5788
|
//# sourceMappingURL=index.mjs.map
|
|
4695
5789
|
//# sourceMappingURL=index.mjs.map
|