masoneffect 0.1.13 → 0.1.14

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.
@@ -3,24 +3,87 @@
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(func, wait) {
8
- let timeout;
9
- return function executedFunction(...args) {
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
- clearTimeout(timeout);
45
+ timeout = null;
12
46
  func.apply(this, args);
13
47
  };
14
- clearTimeout(timeout);
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
- constructor(container, options = {}) {
56
+ container: HTMLElement;
57
+ config: Required<Omit<MasonEffectOptions, 'onReady' | 'onUpdate'>> & {
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
+ handleResize: () => void;
76
+ handleMouseMove: (e: MouseEvent) => void;
77
+ handleMouseLeave: () => void;
78
+ handleMouseDown: () => void;
79
+ handleMouseUp: () => void;
80
+ _debouncedMorph: (textOrOptions?: string | Partial<MasonEffectOptions> | null) => void;
81
+ _debouncedUpdateConfig: (newConfig: Partial<MasonEffectOptions>) => void;
82
+
83
+ constructor(container: HTMLElement | string, options: MasonEffectOptions = {}) {
21
84
  // 컨테이너 요소
22
85
  this.container = typeof container === 'string'
23
- ? document.querySelector(container)
86
+ ? document.querySelector(container) as HTMLElement
24
87
  : container;
25
88
 
26
89
  if (!this.container) {
@@ -38,23 +101,31 @@ export class MasonEffect {
38
101
  repelStrength: options.repelStrength ?? 1,
39
102
  particleColor: options.particleColor || '#fff',
40
103
  fontFamily: options.fontFamily || 'Inter, system-ui, Arial',
41
- fontSize: options.fontSize || null, // null이면 자동 계산
42
- width: options.width || null, // null이면 컨테이너 크기
43
- height: options.height || null, // null이면 컨테이너 크기
44
- devicePixelRatio: options.devicePixelRatio ?? null, // null이면 자동
104
+ fontSize: options.fontSize || null,
105
+ width: options.width || null,
106
+ height: options.height || null,
107
+ devicePixelRatio: options.devicePixelRatio ?? null,
45
108
  onReady: options.onReady || null,
46
109
  onUpdate: options.onUpdate || null,
47
110
  };
48
111
 
49
112
  // 캔버스 생성
50
113
  this.canvas = document.createElement('canvas');
51
- this.ctx = this.canvas.getContext('2d');
114
+ const ctx = this.canvas.getContext('2d');
115
+ if (!ctx) {
116
+ throw new Error('Canvas context not available');
117
+ }
118
+ this.ctx = ctx;
52
119
  this.container.appendChild(this.canvas);
53
120
  this.canvas.style.display = 'block';
54
121
 
55
122
  // 오프스크린 캔버스 (텍스트 렌더링용)
56
123
  this.offCanvas = document.createElement('canvas');
57
- this.offCtx = this.offCanvas.getContext('2d');
124
+ const offCtx = this.offCanvas.getContext('2d');
125
+ if (!offCtx) {
126
+ throw new Error('Offscreen canvas context not available');
127
+ }
128
+ this.offCtx = offCtx;
58
129
 
59
130
  // 상태
60
131
  this.W = 0;
@@ -68,7 +139,7 @@ export class MasonEffect {
68
139
  this.intersectionObserver = null;
69
140
 
70
141
  // 디바운스 설정 (ms)
71
- this.debounceDelay = options.debounceDelay ?? 150; // 기본 150ms
142
+ this.debounceDelay = options.debounceDelay ?? 150;
72
143
 
73
144
  // 이벤트 핸들러 바인딩 (디바운스 적용 전에 바인딩)
74
145
  const boundHandleResize = this.handleResize.bind(this);
@@ -86,7 +157,7 @@ export class MasonEffect {
86
157
  this.init();
87
158
  }
88
159
 
89
- init() {
160
+ init(): void {
90
161
  this.resize();
91
162
  this.setupEventListeners();
92
163
  this.setupIntersectionObserver();
@@ -96,7 +167,7 @@ export class MasonEffect {
96
167
  }
97
168
  }
98
169
 
99
- setupIntersectionObserver() {
170
+ setupIntersectionObserver(): void {
100
171
  // IntersectionObserver가 지원되지 않는 환경에서는 항상 재생
101
172
  if (typeof window === 'undefined' || typeof window.IntersectionObserver === 'undefined') {
102
173
  this.isVisible = true;
@@ -131,7 +202,7 @@ export class MasonEffect {
131
202
  this.intersectionObserver.observe(this.container);
132
203
  }
133
204
 
134
- resize() {
205
+ resize(): void {
135
206
  const width = this.config.width || this.container.clientWidth || window.innerWidth;
136
207
  const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;
137
208
 
@@ -149,7 +220,7 @@ export class MasonEffect {
149
220
  }
150
221
  }
151
222
 
152
- buildTargets() {
223
+ buildTargets(): void {
153
224
  const text = this.config.text;
154
225
  this.offCanvas.width = this.W;
155
226
  this.offCanvas.height = this.H;
@@ -176,7 +247,7 @@ export class MasonEffect {
176
247
  // 픽셀 샘플링
177
248
  const step = Math.max(2, this.config.densityStep);
178
249
  const img = this.offCtx.getImageData(0, 0, this.W, this.H).data;
179
- const targets = [];
250
+ const targets: Array<{ x: number; y: number }> = [];
180
251
 
181
252
  for (let y = 0; y < this.H; y += step) {
182
253
  for (let x = 0; x < this.W; x += step) {
@@ -211,7 +282,7 @@ export class MasonEffect {
211
282
  }
212
283
  }
213
284
 
214
- makeParticle() {
285
+ makeParticle(): Particle {
215
286
  // 캔버스 전체에 골고루 분포 (여백 없이)
216
287
  const sx = Math.random() * this.W;
217
288
  const sy = Math.random() * this.H;
@@ -228,7 +299,7 @@ export class MasonEffect {
228
299
  };
229
300
  }
230
301
 
231
- initParticles() {
302
+ initParticles(): void {
232
303
  // 캔버스 전체에 골고루 분포 (여백 없이)
233
304
  for (const p of this.particles) {
234
305
  const sx = Math.random() * this.W;
@@ -242,7 +313,7 @@ export class MasonEffect {
242
313
  }
243
314
  }
244
315
 
245
- scatter() {
316
+ scatter(): void {
246
317
  // 각 파티클을 초기 위치로 돌아가도록 설정
247
318
  for (const p of this.particles) {
248
319
  // 초기 위치가 저장되어 있으면 그 위치로, 없으면 현재 위치 유지
@@ -259,13 +330,13 @@ export class MasonEffect {
259
330
  }
260
331
  }
261
332
 
262
- morph(textOrOptions = null) {
333
+ morph(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
263
334
  // 즉시 실행이 필요한 경우 (예: 초기화 시)를 위해 내부 메서드 직접 호출
264
335
  // 일반적인 경우에는 디바운스 적용
265
336
  this._debouncedMorph(textOrOptions);
266
337
  }
267
338
 
268
- _morphInternal(textOrOptions = null) {
339
+ _morphInternal(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
269
340
  // W와 H가 0이면 resize 먼저 실행
270
341
  if (this.W === 0 || this.H === 0) {
271
342
  this.resize();
@@ -288,7 +359,7 @@ export class MasonEffect {
288
359
  }
289
360
  }
290
361
 
291
- update() {
362
+ update(): void {
292
363
  this.ctx.clearRect(0, 0, this.W, this.H);
293
364
 
294
365
  for (const p of this.particles) {
@@ -336,19 +407,19 @@ export class MasonEffect {
336
407
  }
337
408
  }
338
409
 
339
- animate() {
410
+ animate(): void {
340
411
  if (!this.isRunning) return;
341
412
  this.update();
342
413
  this.animationId = requestAnimationFrame(() => this.animate());
343
414
  }
344
415
 
345
- start() {
416
+ start(): void {
346
417
  if (this.isRunning) return;
347
418
  this.isRunning = true;
348
419
  this.animate();
349
420
  }
350
421
 
351
- stop() {
422
+ stop(): void {
352
423
  this.isRunning = false;
353
424
  if (this.animationId) {
354
425
  cancelAnimationFrame(this.animationId);
@@ -356,7 +427,7 @@ export class MasonEffect {
356
427
  }
357
428
  }
358
429
 
359
- setupEventListeners() {
430
+ setupEventListeners(): void {
360
431
  window.addEventListener('resize', this.handleResize);
361
432
  this.canvas.addEventListener('mousemove', this.handleMouseMove);
362
433
  this.canvas.addEventListener('mouseleave', this.handleMouseLeave);
@@ -364,7 +435,7 @@ export class MasonEffect {
364
435
  window.addEventListener('mouseup', this.handleMouseUp);
365
436
  }
366
437
 
367
- removeEventListeners() {
438
+ removeEventListeners(): void {
368
439
  window.removeEventListener('resize', this.handleResize);
369
440
  this.canvas.removeEventListener('mousemove', this.handleMouseMove);
370
441
  this.canvas.removeEventListener('mouseleave', this.handleMouseLeave);
@@ -372,35 +443,35 @@ export class MasonEffect {
372
443
  window.removeEventListener('mouseup', this.handleMouseUp);
373
444
  }
374
445
 
375
- handleResize() {
446
+ handleResize(): void {
376
447
  this.resize();
377
448
  }
378
449
 
379
- handleMouseMove(e) {
450
+ handleMouseMove(e: MouseEvent): void {
380
451
  const rect = this.canvas.getBoundingClientRect();
381
452
  this.mouse.x = (e.clientX - rect.left) * this.DPR;
382
453
  this.mouse.y = (e.clientY - rect.top) * this.DPR;
383
454
  }
384
455
 
385
- handleMouseLeave() {
456
+ handleMouseLeave(): void {
386
457
  this.mouse.x = this.mouse.y = 0;
387
458
  }
388
459
 
389
- handleMouseDown() {
460
+ handleMouseDown(): void {
390
461
  this.mouse.down = true;
391
462
  }
392
463
 
393
- handleMouseUp() {
464
+ handleMouseUp(): void {
394
465
  this.mouse.down = false;
395
466
  }
396
467
 
397
468
  // 설정 업데이트
398
- updateConfig(newConfig) {
469
+ updateConfig(newConfig: Partial<MasonEffectOptions>): void {
399
470
  // 디바운스 적용
400
471
  this._debouncedUpdateConfig(newConfig);
401
472
  }
402
473
 
403
- _updateConfigInternal(newConfig) {
474
+ _updateConfigInternal(newConfig: Partial<MasonEffectOptions>): void {
404
475
  this.config = { ...this.config, ...newConfig };
405
476
  if (newConfig.text) {
406
477
  this.buildTargets();
@@ -408,7 +479,7 @@ export class MasonEffect {
408
479
  }
409
480
 
410
481
  // 파괴 및 정리
411
- destroy() {
482
+ destroy(): void {
412
483
  this.stop();
413
484
  this.removeEventListeners();
414
485
  if (this.intersectionObserver) {
@@ -6,6 +6,7 @@ import MasonEffect from './core/index.js';
6
6
 
7
7
  // Named export
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()로 직접 사용 가능
@@ -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
-