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.
@@ -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(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' | '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, // null이면 자동 계산
42
- width: options.width || null, // null이면 컨테이너 크기
43
- height: options.height || null, // null이면 컨테이너 크기
44
- devicePixelRatio: options.devicePixelRatio ?? null, // 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
- this.ctx = this.canvas.getContext('2d');
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
- this.offCtx = this.offCanvas.getContext('2d');
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; // 기본 150ms
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 = null) {
328
+ morph(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
263
329
  // 즉시 실행이 필요한 경우 (예: 초기화 시)를 위해 내부 메서드 직접 호출
264
330
  // 일반적인 경우에는 디바운스 적용
265
331
  this._debouncedMorph(textOrOptions);
266
332
  }
267
333
 
268
- _morphInternal(textOrOptions = null) {
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()로 직접 사용 가능
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MasonEffect - UMD 전용 엔트리 포인트
3
+ * CDN 사용 시: new MasonEffect()로 직접 사용 가능
4
+ */
5
+
6
+ import MasonEffect from './core/index.js';
7
+
8
+ // Default export만 사용 (UMD 빌드에서 전역 변수로 직접 노출됨)
9
+ export default MasonEffect;
10
+
@@ -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
-