masoneffect 0.1.13 → 0.1.15
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/README.md +1 -1
- package/dist/core/index.d.ts +86 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +346 -388
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.js +346 -388
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.umd.d.ts +7 -0
- package/dist/index.umd.d.ts.map +1 -0
- package/dist/index.umd.js +381 -425
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/react/MasonEffect.cjs +346 -388
- package/dist/react/MasonEffect.cjs.map +1 -1
- package/dist/react/MasonEffect.js +346 -388
- package/dist/react/MasonEffect.js.map +1 -1
- package/dist/react/MasonEffect.min.cjs +1 -1
- package/dist/react/MasonEffect.min.js +1 -1
- package/dist/react/core/index.d.ts +86 -0
- package/dist/react/core/index.d.ts.map +1 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.umd.d.ts +7 -0
- package/dist/react/index.umd.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/core/{index.js → index.ts} +105 -39
- package/src/{index.js → index.ts} +2 -1
- package/src/index.umd.ts +10 -0
- package/src/core/index.d.ts +0 -59
|
@@ -3,24 +3,82 @@
|
|
|
3
3
|
* 바닐라 JS 코어 클래스
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
export interface MasonEffectOptions {
|
|
7
|
+
text?: string;
|
|
8
|
+
densityStep?: number;
|
|
9
|
+
maxParticles?: number;
|
|
10
|
+
pointSize?: number;
|
|
11
|
+
ease?: number;
|
|
12
|
+
repelRadius?: number;
|
|
13
|
+
repelStrength?: number;
|
|
14
|
+
particleColor?: string;
|
|
15
|
+
fontFamily?: string;
|
|
16
|
+
fontSize?: number | null;
|
|
17
|
+
width?: number | null;
|
|
18
|
+
height?: number | null;
|
|
19
|
+
devicePixelRatio?: number | null;
|
|
20
|
+
debounceDelay?: number;
|
|
21
|
+
onReady?: (instance: MasonEffect) => void;
|
|
22
|
+
onUpdate?: (instance: MasonEffect) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Particle {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
vx: number;
|
|
29
|
+
vy: number;
|
|
30
|
+
tx: number;
|
|
31
|
+
ty: number;
|
|
32
|
+
initialX?: number;
|
|
33
|
+
initialY?: number;
|
|
34
|
+
j: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
6
37
|
// 디바운스 유틸리티 함수
|
|
7
|
-
function debounce(
|
|
8
|
-
|
|
9
|
-
|
|
38
|
+
function debounce<T extends (...args: any[]) => any>(
|
|
39
|
+
func: T,
|
|
40
|
+
wait: number
|
|
41
|
+
): (...args: Parameters<T>) => void {
|
|
42
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
43
|
+
return function executedFunction(...args: Parameters<T>) {
|
|
10
44
|
const later = () => {
|
|
11
|
-
|
|
45
|
+
timeout = null;
|
|
12
46
|
func.apply(this, args);
|
|
13
47
|
};
|
|
14
|
-
|
|
48
|
+
if (timeout !== null) {
|
|
49
|
+
clearTimeout(timeout);
|
|
50
|
+
}
|
|
15
51
|
timeout = setTimeout(later, wait);
|
|
16
52
|
};
|
|
17
53
|
}
|
|
18
54
|
|
|
19
55
|
export class MasonEffect {
|
|
20
|
-
|
|
56
|
+
container: HTMLElement;
|
|
57
|
+
config: Required<Omit<MasonEffectOptions, 'onReady' | 'onUpdate' | 'debounceDelay'>> & {
|
|
58
|
+
onReady: MasonEffectOptions['onReady'];
|
|
59
|
+
onUpdate: MasonEffectOptions['onUpdate'];
|
|
60
|
+
};
|
|
61
|
+
canvas: HTMLCanvasElement;
|
|
62
|
+
ctx: CanvasRenderingContext2D;
|
|
63
|
+
offCanvas: HTMLCanvasElement;
|
|
64
|
+
offCtx: CanvasRenderingContext2D;
|
|
65
|
+
W: number;
|
|
66
|
+
H: number;
|
|
67
|
+
DPR: number;
|
|
68
|
+
particles: Particle[];
|
|
69
|
+
mouse: { x: number; y: number; down: boolean };
|
|
70
|
+
animationId: number | null;
|
|
71
|
+
isRunning: boolean;
|
|
72
|
+
isVisible: boolean;
|
|
73
|
+
intersectionObserver: IntersectionObserver | null;
|
|
74
|
+
debounceDelay: number;
|
|
75
|
+
_debouncedMorph!: (textOrOptions?: string | Partial<MasonEffectOptions> | null) => void;
|
|
76
|
+
_debouncedUpdateConfig!: (newConfig: Partial<MasonEffectOptions>) => void;
|
|
77
|
+
|
|
78
|
+
constructor(container: HTMLElement | string, options: MasonEffectOptions = {}) {
|
|
21
79
|
// 컨테이너 요소
|
|
22
80
|
this.container = typeof container === 'string'
|
|
23
|
-
? document.querySelector(container)
|
|
81
|
+
? document.querySelector(container) as HTMLElement
|
|
24
82
|
: container;
|
|
25
83
|
|
|
26
84
|
if (!this.container) {
|
|
@@ -38,23 +96,31 @@ export class MasonEffect {
|
|
|
38
96
|
repelStrength: options.repelStrength ?? 1,
|
|
39
97
|
particleColor: options.particleColor || '#fff',
|
|
40
98
|
fontFamily: options.fontFamily || 'Inter, system-ui, Arial',
|
|
41
|
-
fontSize: options.fontSize || null,
|
|
42
|
-
width: options.width || null,
|
|
43
|
-
height: options.height || null,
|
|
44
|
-
devicePixelRatio: options.devicePixelRatio ?? null,
|
|
99
|
+
fontSize: options.fontSize || null,
|
|
100
|
+
width: options.width || null,
|
|
101
|
+
height: options.height || null,
|
|
102
|
+
devicePixelRatio: options.devicePixelRatio ?? null,
|
|
45
103
|
onReady: options.onReady || null,
|
|
46
104
|
onUpdate: options.onUpdate || null,
|
|
47
|
-
};
|
|
105
|
+
} as typeof this.config;
|
|
48
106
|
|
|
49
107
|
// 캔버스 생성
|
|
50
108
|
this.canvas = document.createElement('canvas');
|
|
51
|
-
|
|
109
|
+
const ctx = this.canvas.getContext('2d');
|
|
110
|
+
if (!ctx) {
|
|
111
|
+
throw new Error('Canvas context not available');
|
|
112
|
+
}
|
|
113
|
+
this.ctx = ctx;
|
|
52
114
|
this.container.appendChild(this.canvas);
|
|
53
115
|
this.canvas.style.display = 'block';
|
|
54
116
|
|
|
55
117
|
// 오프스크린 캔버스 (텍스트 렌더링용)
|
|
56
118
|
this.offCanvas = document.createElement('canvas');
|
|
57
|
-
|
|
119
|
+
const offCtx = this.offCanvas.getContext('2d');
|
|
120
|
+
if (!offCtx) {
|
|
121
|
+
throw new Error('Offscreen canvas context not available');
|
|
122
|
+
}
|
|
123
|
+
this.offCtx = offCtx;
|
|
58
124
|
|
|
59
125
|
// 상태
|
|
60
126
|
this.W = 0;
|
|
@@ -68,7 +134,7 @@ export class MasonEffect {
|
|
|
68
134
|
this.intersectionObserver = null;
|
|
69
135
|
|
|
70
136
|
// 디바운스 설정 (ms)
|
|
71
|
-
this.debounceDelay = options.debounceDelay ?? 150;
|
|
137
|
+
this.debounceDelay = options.debounceDelay ?? 150;
|
|
72
138
|
|
|
73
139
|
// 이벤트 핸들러 바인딩 (디바운스 적용 전에 바인딩)
|
|
74
140
|
const boundHandleResize = this.handleResize.bind(this);
|
|
@@ -86,7 +152,7 @@ export class MasonEffect {
|
|
|
86
152
|
this.init();
|
|
87
153
|
}
|
|
88
154
|
|
|
89
|
-
init() {
|
|
155
|
+
init(): void {
|
|
90
156
|
this.resize();
|
|
91
157
|
this.setupEventListeners();
|
|
92
158
|
this.setupIntersectionObserver();
|
|
@@ -96,7 +162,7 @@ export class MasonEffect {
|
|
|
96
162
|
}
|
|
97
163
|
}
|
|
98
164
|
|
|
99
|
-
setupIntersectionObserver() {
|
|
165
|
+
setupIntersectionObserver(): void {
|
|
100
166
|
// IntersectionObserver가 지원되지 않는 환경에서는 항상 재생
|
|
101
167
|
if (typeof window === 'undefined' || typeof window.IntersectionObserver === 'undefined') {
|
|
102
168
|
this.isVisible = true;
|
|
@@ -131,7 +197,7 @@ export class MasonEffect {
|
|
|
131
197
|
this.intersectionObserver.observe(this.container);
|
|
132
198
|
}
|
|
133
199
|
|
|
134
|
-
resize() {
|
|
200
|
+
resize(): void {
|
|
135
201
|
const width = this.config.width || this.container.clientWidth || window.innerWidth;
|
|
136
202
|
const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;
|
|
137
203
|
|
|
@@ -149,7 +215,7 @@ export class MasonEffect {
|
|
|
149
215
|
}
|
|
150
216
|
}
|
|
151
217
|
|
|
152
|
-
buildTargets() {
|
|
218
|
+
buildTargets(): void {
|
|
153
219
|
const text = this.config.text;
|
|
154
220
|
this.offCanvas.width = this.W;
|
|
155
221
|
this.offCanvas.height = this.H;
|
|
@@ -176,7 +242,7 @@ export class MasonEffect {
|
|
|
176
242
|
// 픽셀 샘플링
|
|
177
243
|
const step = Math.max(2, this.config.densityStep);
|
|
178
244
|
const img = this.offCtx.getImageData(0, 0, this.W, this.H).data;
|
|
179
|
-
const targets = [];
|
|
245
|
+
const targets: Array<{ x: number; y: number }> = [];
|
|
180
246
|
|
|
181
247
|
for (let y = 0; y < this.H; y += step) {
|
|
182
248
|
for (let x = 0; x < this.W; x += step) {
|
|
@@ -211,7 +277,7 @@ export class MasonEffect {
|
|
|
211
277
|
}
|
|
212
278
|
}
|
|
213
279
|
|
|
214
|
-
makeParticle() {
|
|
280
|
+
makeParticle(): Particle {
|
|
215
281
|
// 캔버스 전체에 골고루 분포 (여백 없이)
|
|
216
282
|
const sx = Math.random() * this.W;
|
|
217
283
|
const sy = Math.random() * this.H;
|
|
@@ -228,7 +294,7 @@ export class MasonEffect {
|
|
|
228
294
|
};
|
|
229
295
|
}
|
|
230
296
|
|
|
231
|
-
initParticles() {
|
|
297
|
+
initParticles(): void {
|
|
232
298
|
// 캔버스 전체에 골고루 분포 (여백 없이)
|
|
233
299
|
for (const p of this.particles) {
|
|
234
300
|
const sx = Math.random() * this.W;
|
|
@@ -242,7 +308,7 @@ export class MasonEffect {
|
|
|
242
308
|
}
|
|
243
309
|
}
|
|
244
310
|
|
|
245
|
-
scatter() {
|
|
311
|
+
scatter(): void {
|
|
246
312
|
// 각 파티클을 초기 위치로 돌아가도록 설정
|
|
247
313
|
for (const p of this.particles) {
|
|
248
314
|
// 초기 위치가 저장되어 있으면 그 위치로, 없으면 현재 위치 유지
|
|
@@ -259,13 +325,13 @@ export class MasonEffect {
|
|
|
259
325
|
}
|
|
260
326
|
}
|
|
261
327
|
|
|
262
|
-
morph(textOrOptions
|
|
328
|
+
morph(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
|
|
263
329
|
// 즉시 실행이 필요한 경우 (예: 초기화 시)를 위해 내부 메서드 직접 호출
|
|
264
330
|
// 일반적인 경우에는 디바운스 적용
|
|
265
331
|
this._debouncedMorph(textOrOptions);
|
|
266
332
|
}
|
|
267
333
|
|
|
268
|
-
_morphInternal(textOrOptions
|
|
334
|
+
_morphInternal(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
|
|
269
335
|
// W와 H가 0이면 resize 먼저 실행
|
|
270
336
|
if (this.W === 0 || this.H === 0) {
|
|
271
337
|
this.resize();
|
|
@@ -288,7 +354,7 @@ export class MasonEffect {
|
|
|
288
354
|
}
|
|
289
355
|
}
|
|
290
356
|
|
|
291
|
-
update() {
|
|
357
|
+
update(): void {
|
|
292
358
|
this.ctx.clearRect(0, 0, this.W, this.H);
|
|
293
359
|
|
|
294
360
|
for (const p of this.particles) {
|
|
@@ -336,19 +402,19 @@ export class MasonEffect {
|
|
|
336
402
|
}
|
|
337
403
|
}
|
|
338
404
|
|
|
339
|
-
animate() {
|
|
405
|
+
animate(): void {
|
|
340
406
|
if (!this.isRunning) return;
|
|
341
407
|
this.update();
|
|
342
408
|
this.animationId = requestAnimationFrame(() => this.animate());
|
|
343
409
|
}
|
|
344
410
|
|
|
345
|
-
start() {
|
|
411
|
+
start(): void {
|
|
346
412
|
if (this.isRunning) return;
|
|
347
413
|
this.isRunning = true;
|
|
348
414
|
this.animate();
|
|
349
415
|
}
|
|
350
416
|
|
|
351
|
-
stop() {
|
|
417
|
+
stop(): void {
|
|
352
418
|
this.isRunning = false;
|
|
353
419
|
if (this.animationId) {
|
|
354
420
|
cancelAnimationFrame(this.animationId);
|
|
@@ -356,7 +422,7 @@ export class MasonEffect {
|
|
|
356
422
|
}
|
|
357
423
|
}
|
|
358
424
|
|
|
359
|
-
setupEventListeners() {
|
|
425
|
+
setupEventListeners(): void {
|
|
360
426
|
window.addEventListener('resize', this.handleResize);
|
|
361
427
|
this.canvas.addEventListener('mousemove', this.handleMouseMove);
|
|
362
428
|
this.canvas.addEventListener('mouseleave', this.handleMouseLeave);
|
|
@@ -364,7 +430,7 @@ export class MasonEffect {
|
|
|
364
430
|
window.addEventListener('mouseup', this.handleMouseUp);
|
|
365
431
|
}
|
|
366
432
|
|
|
367
|
-
removeEventListeners() {
|
|
433
|
+
removeEventListeners(): void {
|
|
368
434
|
window.removeEventListener('resize', this.handleResize);
|
|
369
435
|
this.canvas.removeEventListener('mousemove', this.handleMouseMove);
|
|
370
436
|
this.canvas.removeEventListener('mouseleave', this.handleMouseLeave);
|
|
@@ -372,35 +438,35 @@ export class MasonEffect {
|
|
|
372
438
|
window.removeEventListener('mouseup', this.handleMouseUp);
|
|
373
439
|
}
|
|
374
440
|
|
|
375
|
-
handleResize() {
|
|
441
|
+
handleResize(): void {
|
|
376
442
|
this.resize();
|
|
377
443
|
}
|
|
378
444
|
|
|
379
|
-
handleMouseMove(e) {
|
|
445
|
+
handleMouseMove(e: MouseEvent): void {
|
|
380
446
|
const rect = this.canvas.getBoundingClientRect();
|
|
381
447
|
this.mouse.x = (e.clientX - rect.left) * this.DPR;
|
|
382
448
|
this.mouse.y = (e.clientY - rect.top) * this.DPR;
|
|
383
449
|
}
|
|
384
450
|
|
|
385
|
-
handleMouseLeave() {
|
|
451
|
+
handleMouseLeave(): void {
|
|
386
452
|
this.mouse.x = this.mouse.y = 0;
|
|
387
453
|
}
|
|
388
454
|
|
|
389
|
-
handleMouseDown() {
|
|
455
|
+
handleMouseDown(): void {
|
|
390
456
|
this.mouse.down = true;
|
|
391
457
|
}
|
|
392
458
|
|
|
393
|
-
handleMouseUp() {
|
|
459
|
+
handleMouseUp(): void {
|
|
394
460
|
this.mouse.down = false;
|
|
395
461
|
}
|
|
396
462
|
|
|
397
463
|
// 설정 업데이트
|
|
398
|
-
updateConfig(newConfig) {
|
|
464
|
+
updateConfig(newConfig: Partial<MasonEffectOptions>): void {
|
|
399
465
|
// 디바운스 적용
|
|
400
466
|
this._debouncedUpdateConfig(newConfig);
|
|
401
467
|
}
|
|
402
468
|
|
|
403
|
-
_updateConfigInternal(newConfig) {
|
|
469
|
+
_updateConfigInternal(newConfig: Partial<MasonEffectOptions>): void {
|
|
404
470
|
this.config = { ...this.config, ...newConfig };
|
|
405
471
|
if (newConfig.text) {
|
|
406
472
|
this.buildTargets();
|
|
@@ -408,7 +474,7 @@ export class MasonEffect {
|
|
|
408
474
|
}
|
|
409
475
|
|
|
410
476
|
// 파괴 및 정리
|
|
411
|
-
destroy() {
|
|
477
|
+
destroy(): void {
|
|
412
478
|
this.stop();
|
|
413
479
|
this.removeEventListeners();
|
|
414
480
|
if (this.intersectionObserver) {
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import MasonEffect from './core/index.js';
|
|
6
6
|
|
|
7
|
-
// Named export
|
|
7
|
+
// Named export (ES modules, CommonJS용)
|
|
8
8
|
export { MasonEffect };
|
|
9
|
+
export type { MasonEffectOptions, Particle } from './core/index.js';
|
|
9
10
|
|
|
10
11
|
// Default export (UMD 빌드에서 전역 변수로 직접 노출됨)
|
|
11
12
|
// CDN 사용 시: new MasonEffect()로 직접 사용 가능
|
package/src/index.umd.ts
ADDED
package/src/core/index.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MasonEffect 타입 정의
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface MasonEffectOptions {
|
|
6
|
-
text?: string;
|
|
7
|
-
densityStep?: number;
|
|
8
|
-
maxParticles?: number;
|
|
9
|
-
pointSize?: number;
|
|
10
|
-
ease?: number;
|
|
11
|
-
repelRadius?: number;
|
|
12
|
-
repelStrength?: number;
|
|
13
|
-
particleColor?: string;
|
|
14
|
-
fontFamily?: string;
|
|
15
|
-
fontSize?: number | null;
|
|
16
|
-
width?: number | null;
|
|
17
|
-
height?: number | null;
|
|
18
|
-
devicePixelRatio?: number | null;
|
|
19
|
-
onReady?: (instance: MasonEffect) => void;
|
|
20
|
-
onUpdate?: (instance: MasonEffect) => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface Particle {
|
|
24
|
-
x: number;
|
|
25
|
-
y: number;
|
|
26
|
-
vx: number;
|
|
27
|
-
vy: number;
|
|
28
|
-
tx: number;
|
|
29
|
-
ty: number;
|
|
30
|
-
j: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class MasonEffect {
|
|
34
|
-
constructor(container: HTMLElement | string, options?: MasonEffectOptions);
|
|
35
|
-
|
|
36
|
-
config: MasonEffectOptions;
|
|
37
|
-
canvas: HTMLCanvasElement;
|
|
38
|
-
ctx: CanvasRenderingContext2D;
|
|
39
|
-
particles: Particle[];
|
|
40
|
-
mouse: { x: number; y: number; down: boolean };
|
|
41
|
-
isRunning: boolean;
|
|
42
|
-
|
|
43
|
-
init(): void;
|
|
44
|
-
resize(): void;
|
|
45
|
-
buildTargets(): void;
|
|
46
|
-
makeParticle(): Particle;
|
|
47
|
-
initParticles(): void;
|
|
48
|
-
scatter(): void;
|
|
49
|
-
morph(textOrOptions?: string | Partial<MasonEffectOptions>): void;
|
|
50
|
-
update(): void;
|
|
51
|
-
animate(): void;
|
|
52
|
-
start(): void;
|
|
53
|
-
stop(): void;
|
|
54
|
-
updateConfig(newConfig: Partial<MasonEffectOptions>): void;
|
|
55
|
-
destroy(): void;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export default MasonEffect;
|
|
59
|
-
|