masoneffect 0.1.18 → 0.1.20

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.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/dist/core/index.d.ts.map +1 -1
  3. package/dist/index.cjs +1 -0
  4. package/dist/index.mjs +1 -0
  5. package/dist/react/MasonEffect.cjs +1 -526
  6. package/dist/react/MasonEffect.d.ts.map +1 -1
  7. package/dist/react/MasonEffect.mjs +1 -0
  8. package/dist/react/core/index.d.ts +0 -1
  9. package/dist/react/core/index.d.ts.map +1 -1
  10. package/dist/react/index.d.ts +2 -8
  11. package/dist/react/index.mjs +1 -0
  12. package/dist/react/index.umd.d.ts +0 -1
  13. package/dist/react/react/MasonEffect.d.ts +0 -1
  14. package/dist/react/react/MasonEffect.d.ts.map +1 -1
  15. package/dist/react/react/index.d.ts +2 -0
  16. package/dist/react/react/index.d.ts.map +1 -0
  17. package/dist/react/vue/index.d.ts +5 -0
  18. package/dist/react/vue/index.d.ts.map +1 -0
  19. package/dist/vue/index.cjs +1 -0
  20. package/dist/vue/index.d.ts +90 -0
  21. package/{src/core/index.ts → dist/vue/index.mjs} +227 -237
  22. package/package.json +14 -15
  23. package/dist/index.esm.js +0 -387
  24. package/dist/index.esm.js.map +0 -1
  25. package/dist/index.esm.min.js +0 -1
  26. package/dist/index.js +0 -392
  27. package/dist/index.js.map +0 -1
  28. package/dist/index.min.js +0 -1
  29. package/dist/index.umd.js +0 -396
  30. package/dist/index.umd.js.map +0 -1
  31. package/dist/index.umd.min.js +0 -1
  32. package/dist/react/MasonEffect.cjs.map +0 -1
  33. package/dist/react/MasonEffect.js +0 -524
  34. package/dist/react/MasonEffect.js.map +0 -1
  35. package/dist/react/MasonEffect.min.cjs +0 -1
  36. package/dist/react/MasonEffect.min.js +0 -1
  37. package/src/index.ts +0 -14
  38. package/src/index.umd.ts +0 -10
  39. package/src/react/MasonEffect.tsx +0 -228
  40. package/src/react/index.js +0 -2
  41. package/src/vue/MasonEffect.vue +0 -140
@@ -1,46 +1,7 @@
1
- /**
2
- * MasonEffect - 파티클 모핑 효과 라이브러리
3
- * 바닐라 JS 코어 클래스
4
- */
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
-
37
- // 디바운스 유틸리티 함수
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>) {
1
+ import { defineComponent, ref, watch, onMounted, onBeforeUnmount, createElementBlock, openBlock, normalizeStyle, normalizeClass } from "vue";
2
+ function debounce(func, wait) {
3
+ let timeout = null;
4
+ return function executedFunction(...args) {
44
5
  const later = () => {
45
6
  timeout = null;
46
7
  func.apply(this, args);
@@ -51,78 +12,43 @@ function debounce<T extends (...args: any[]) => any>(
51
12
  timeout = setTimeout(later, wait);
52
13
  };
53
14
  }
54
-
55
- export class MasonEffect {
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 = {}) {
79
- // 컨테이너 요소
80
- this.container = typeof container === 'string'
81
- ? document.querySelector(container) as HTMLElement
82
- : container;
83
-
15
+ class MasonEffect {
16
+ constructor(container, options = {}) {
17
+ this.container = typeof container === "string" ? document.querySelector(container) : container;
84
18
  if (!this.container) {
85
- throw new Error('Container element not found');
19
+ throw new Error("Container element not found");
86
20
  }
87
-
88
- // 설정값들
89
21
  this.config = {
90
- text: options.text || 'mason effect',
22
+ text: options.text || "mason effect",
91
23
  densityStep: options.densityStep ?? 2,
92
24
  maxParticles: options.maxParticles ?? 3200,
93
25
  pointSize: options.pointSize ?? 0.5,
94
26
  ease: options.ease ?? 0.05,
95
27
  repelRadius: options.repelRadius ?? 150,
96
28
  repelStrength: options.repelStrength ?? 1,
97
- particleColor: options.particleColor || '#fff',
98
- fontFamily: options.fontFamily || 'Inter, system-ui, Arial',
29
+ particleColor: options.particleColor || "#fff",
30
+ fontFamily: options.fontFamily || "Inter, system-ui, Arial",
99
31
  fontSize: options.fontSize || null,
100
32
  width: options.width || null,
101
33
  height: options.height || null,
102
34
  devicePixelRatio: options.devicePixelRatio ?? null,
103
35
  onReady: options.onReady || null,
104
- onUpdate: options.onUpdate || null,
105
- } as typeof this.config;
106
-
107
- // 캔버스 생성
108
- this.canvas = document.createElement('canvas');
109
- const ctx = this.canvas.getContext('2d');
36
+ onUpdate: options.onUpdate || null
37
+ };
38
+ this.canvas = document.createElement("canvas");
39
+ const ctx = this.canvas.getContext("2d");
110
40
  if (!ctx) {
111
- throw new Error('Canvas context not available');
41
+ throw new Error("Canvas context not available");
112
42
  }
113
43
  this.ctx = ctx;
114
44
  this.container.appendChild(this.canvas);
115
- this.canvas.style.display = 'block';
116
-
117
- // 오프스크린 캔버스 (텍스트 렌더링용)
118
- this.offCanvas = document.createElement('canvas');
119
- const offCtx = this.offCanvas.getContext('2d');
45
+ this.canvas.style.display = "block";
46
+ this.offCanvas = document.createElement("canvas");
47
+ const offCtx = this.offCanvas.getContext("2d");
120
48
  if (!offCtx) {
121
- throw new Error('Offscreen canvas context not available');
49
+ throw new Error("Offscreen canvas context not available");
122
50
  }
123
51
  this.offCtx = offCtx;
124
-
125
- // 상태
126
52
  this.W = 0;
127
53
  this.H = 0;
128
54
  this.DPR = this.config.devicePixelRatio || Math.min(window.devicePixelRatio || 1, 1.8);
@@ -132,54 +58,38 @@ export class MasonEffect {
132
58
  this.isRunning = false;
133
59
  this.isVisible = false;
134
60
  this.intersectionObserver = null;
135
-
136
- // 디바운스 설정 (ms)
137
61
  this.debounceDelay = options.debounceDelay ?? 150;
138
-
139
- // 이벤트 핸들러 바인딩 (디바운스 적용 전에 바인딩)
140
62
  const boundHandleResize = this.handleResize.bind(this);
141
63
  this.handleResize = debounce(boundHandleResize, this.debounceDelay);
142
64
  this.handleMouseMove = this.handleMouseMove.bind(this);
143
65
  this.handleMouseLeave = this.handleMouseLeave.bind(this);
144
66
  this.handleMouseDown = this.handleMouseDown.bind(this);
145
67
  this.handleMouseUp = this.handleMouseUp.bind(this);
146
-
147
- // morph와 updateConfig를 위한 디바운스된 내부 메서드
148
68
  this._debouncedMorph = debounce(this._morphInternal.bind(this), this.debounceDelay);
149
69
  this._debouncedUpdateConfig = debounce(this._updateConfigInternal.bind(this), this.debounceDelay);
150
-
151
- // 초기화
152
70
  this.init();
153
71
  }
154
-
155
- init(): void {
72
+ init() {
156
73
  this.resize();
157
74
  this.setupEventListeners();
158
75
  this.setupIntersectionObserver();
159
-
160
76
  if (this.config.onReady) {
161
77
  this.config.onReady(this);
162
78
  }
163
79
  }
164
-
165
- setupIntersectionObserver(): void {
166
- // IntersectionObserver가 지원되지 않는 환경에서는 항상 재생
167
- if (typeof window === 'undefined' || typeof window.IntersectionObserver === 'undefined') {
80
+ setupIntersectionObserver() {
81
+ if (typeof window === "undefined" || typeof window.IntersectionObserver === "undefined") {
168
82
  this.isVisible = true;
169
83
  this.start();
170
84
  return;
171
85
  }
172
-
173
- // 이미 설정되어 있다면 다시 만들지 않음
174
86
  if (this.intersectionObserver) {
175
87
  return;
176
88
  }
177
-
178
89
  this.intersectionObserver = new IntersectionObserver(
179
90
  (entries) => {
180
91
  for (const entry of entries) {
181
92
  if (entry.target !== this.container) continue;
182
-
183
93
  if (entry.isIntersecting) {
184
94
  this.isVisible = true;
185
95
  this.start();
@@ -190,75 +100,74 @@ export class MasonEffect {
190
100
  }
191
101
  },
192
102
  {
193
- threshold: 0.1, // 10% 이상 보일 때 동작
103
+ threshold: 0.1
104
+ // 10% 이상 보일 때 동작
194
105
  }
195
106
  );
196
-
197
107
  this.intersectionObserver.observe(this.container);
198
108
  }
199
-
200
- resize(): void {
109
+ resize() {
201
110
  const width = this.config.width || this.container.clientWidth || window.innerWidth;
202
111
  const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;
203
-
112
+ if (width <= 0 || height <= 0) {
113
+ return;
114
+ }
204
115
  this.W = Math.floor(width * this.DPR);
205
116
  this.H = Math.floor(height * this.DPR);
206
-
117
+ const MAX_CANVAS_SIZE = 4096;
118
+ if (this.W > MAX_CANVAS_SIZE || this.H > MAX_CANVAS_SIZE) {
119
+ const scale = Math.min(MAX_CANVAS_SIZE / this.W, MAX_CANVAS_SIZE / this.H);
120
+ this.W = Math.floor(this.W * scale);
121
+ this.H = Math.floor(this.H * scale);
122
+ this.DPR = this.DPR * scale;
123
+ }
207
124
  this.canvas.width = this.W;
208
125
  this.canvas.height = this.H;
209
- this.canvas.style.width = width + 'px';
210
- this.canvas.style.height = height + 'px';
211
-
212
- this.buildTargets();
213
- if (!this.particles.length) {
214
- this.initParticles();
126
+ this.canvas.style.width = width + "px";
127
+ this.canvas.style.height = height + "px";
128
+ if (this.W > 0 && this.H > 0) {
129
+ this.buildTargets();
130
+ if (!this.particles.length) {
131
+ this.initParticles();
132
+ }
215
133
  }
216
134
  }
217
-
218
- buildTargets(): void {
135
+ buildTargets() {
136
+ if (this.W <= 0 || this.H <= 0) {
137
+ return;
138
+ }
219
139
  const text = this.config.text;
220
140
  this.offCanvas.width = this.W;
221
141
  this.offCanvas.height = this.H;
222
142
  this.offCtx.clearRect(0, 0, this.offCanvas.width, this.offCanvas.height);
223
-
224
143
  const base = Math.min(this.W, this.H);
225
144
  const fontSize = this.config.fontSize || Math.max(80, Math.floor(base * 0.18));
226
- this.offCtx.fillStyle = '#ffffff';
227
- this.offCtx.textAlign = 'center';
228
- this.offCtx.textBaseline = 'middle';
145
+ this.offCtx.fillStyle = "#ffffff";
146
+ this.offCtx.textAlign = "center";
147
+ this.offCtx.textBaseline = "middle";
229
148
  this.offCtx.font = `400 ${fontSize}px ${this.config.fontFamily}`;
230
-
231
- // 글자 간격 계산 및 그리기
232
- const chars = text.split('');
149
+ const chars = text.split("");
233
150
  const spacing = fontSize * 0.05;
234
151
  const totalWidth = this.offCtx.measureText(text).width + spacing * (chars.length - 1);
235
152
  let x = this.W / 2 - totalWidth / 2;
236
-
237
153
  for (const ch of chars) {
238
154
  this.offCtx.fillText(ch, x + this.offCtx.measureText(ch).width / 2, this.H / 2);
239
155
  x += this.offCtx.measureText(ch).width + spacing;
240
156
  }
241
-
242
- // 픽셀 샘플링
243
157
  const step = Math.max(2, this.config.densityStep);
244
158
  const img = this.offCtx.getImageData(0, 0, this.W, this.H).data;
245
- const targets: Array<{ x: number; y: number }> = [];
246
-
159
+ const targets = [];
247
160
  for (let y = 0; y < this.H; y += step) {
248
- for (let x = 0; x < this.W; x += step) {
249
- const i = (y * this.W + x) * 4;
161
+ for (let x2 = 0; x2 < this.W; x2 += step) {
162
+ const i = (y * this.W + x2) * 4;
250
163
  if (img[i] + img[i + 1] + img[i + 2] > 600) {
251
- targets.push({ x, y });
164
+ targets.push({ x: x2, y });
252
165
  }
253
166
  }
254
167
  }
255
-
256
- // 파티클 수 제한
257
168
  while (targets.length > this.config.maxParticles) {
258
169
  targets.splice(Math.floor(Math.random() * targets.length), 1);
259
170
  }
260
-
261
- // 파티클 수 조정
262
171
  if (this.particles.length < targets.length) {
263
172
  const need = targets.length - this.particles.length;
264
173
  for (let i = 0; i < need; i++) {
@@ -267,8 +176,6 @@ export class MasonEffect {
267
176
  } else if (this.particles.length > targets.length) {
268
177
  this.particles.length = targets.length;
269
178
  }
270
-
271
- // 목표 좌표 할당
272
179
  for (let i = 0; i < this.particles.length; i++) {
273
180
  const p = this.particles[i];
274
181
  const t = targets[i];
@@ -276,9 +183,7 @@ export class MasonEffect {
276
183
  p.ty = t.y;
277
184
  }
278
185
  }
279
-
280
- makeParticle(): Particle {
281
- // 캔버스 전체에 골고루 분포 (여백 없이)
186
+ makeParticle() {
282
187
  const sx = Math.random() * this.W;
283
188
  const sy = Math.random() * this.H;
284
189
  return {
@@ -288,35 +193,29 @@ export class MasonEffect {
288
193
  vy: 0,
289
194
  tx: sx,
290
195
  ty: sy,
291
- initialX: sx, // 초기 위치 저장 (scatter 시 돌아갈 위치)
196
+ initialX: sx,
197
+ // 초기 위치 저장 (scatter 시 돌아갈 위치)
292
198
  initialY: sy,
293
- j: Math.random() * Math.PI * 2,
199
+ j: Math.random() * Math.PI * 2
294
200
  };
295
201
  }
296
-
297
- initParticles(): void {
298
- // 캔버스 전체에 골고루 분포 (여백 없이)
202
+ initParticles() {
299
203
  for (const p of this.particles) {
300
204
  const sx = Math.random() * this.W;
301
205
  const sy = Math.random() * this.H;
302
206
  p.x = sx;
303
207
  p.y = sy;
304
208
  p.vx = p.vy = 0;
305
- // 초기 위치 저장 (scatter 시 돌아갈 위치)
306
209
  p.initialX = sx;
307
210
  p.initialY = sy;
308
211
  }
309
212
  }
310
-
311
- scatter(): void {
312
- // 각 파티클을 초기 위치로 돌아가도록 설정
213
+ scatter() {
313
214
  for (const p of this.particles) {
314
- // 초기 위치가 저장되어 있으면 위치로, 없으면 현재 위치 유지
315
- if (p.initialX !== undefined && p.initialY !== undefined) {
215
+ if (p.initialX !== void 0 && p.initialY !== void 0) {
316
216
  p.tx = p.initialX;
317
217
  p.ty = p.initialY;
318
218
  } else {
319
- // 초기 위치가 없으면 현재 위치를 초기 위치로 저장
320
219
  p.initialX = p.x;
321
220
  p.initialY = p.y;
322
221
  p.tx = p.initialX;
@@ -324,71 +223,51 @@ export class MasonEffect {
324
223
  }
325
224
  }
326
225
  }
327
-
328
- morph(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
329
- // 즉시 실행이 필요한 경우 (예: 초기화 시)를 위해 내부 메서드 직접 호출
330
- // 일반적인 경우에는 디바운스 적용
226
+ morph(textOrOptions) {
331
227
  this._debouncedMorph(textOrOptions);
332
228
  }
333
-
334
- _morphInternal(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
335
- // W와 H가 0이면 resize 먼저 실행
229
+ _morphInternal(textOrOptions) {
336
230
  if (this.W === 0 || this.H === 0) {
337
231
  this.resize();
338
232
  }
339
-
340
- if (typeof textOrOptions === 'string') {
341
- // 문자열인 경우: 기존 동작 유지
233
+ if (typeof textOrOptions === "string") {
342
234
  this.config.text = textOrOptions;
343
235
  this.buildTargets();
344
- } else if (textOrOptions && typeof textOrOptions === 'object') {
345
- // 객체인 경우: 텍스트와 함께 다른 설정도 변경
346
- const needsRebuild = textOrOptions.text !== undefined;
236
+ } else if (textOrOptions && typeof textOrOptions === "object") {
237
+ const needsRebuild = textOrOptions.text !== void 0;
347
238
  this.config = { ...this.config, ...textOrOptions };
348
239
  if (needsRebuild) {
349
240
  this.buildTargets();
350
241
  }
351
242
  } else {
352
- // null이거나 undefined인 경우: 현재 텍스트로 재빌드
353
243
  this.buildTargets();
354
244
  }
355
245
  }
356
-
357
- update(): void {
246
+ update() {
358
247
  this.ctx.clearRect(0, 0, this.W, this.H);
359
-
360
248
  for (const p of this.particles) {
361
- // 목표 좌표로 당기는 힘
362
249
  let ax = (p.tx - p.x) * this.config.ease;
363
250
  let ay = (p.ty - p.y) * this.config.ease;
364
-
365
- // 마우스 반발/흡입
366
251
  if (this.mouse.x || this.mouse.y) {
367
252
  const dx = p.x - this.mouse.x;
368
253
  const dy = p.y - this.mouse.y;
369
254
  const d2 = dx * dx + dy * dy;
370
- const r = this.config.repelRadius * this.DPR;
371
- if (d2 < r * r) {
372
- const d = Math.sqrt(d2) + 0.0001;
373
- const f = (this.mouse.down ? -1 : 1) * this.config.repelStrength * (1 - d / r);
374
- ax += (dx / d) * f * 6.0;
375
- ay += (dy / d) * f * 6.0;
255
+ const r2 = this.config.repelRadius * this.DPR;
256
+ if (d2 < r2 * r2) {
257
+ const d = Math.sqrt(d2) + 1e-4;
258
+ const f = (this.mouse.down ? -1 : 1) * this.config.repelStrength * (1 - d / r2);
259
+ ax += dx / d * f * 6;
260
+ ay += dy / d * f * 6;
376
261
  }
377
262
  }
378
-
379
- // 진동 효과
380
263
  p.j += 2;
381
264
  ax += Math.cos(p.j) * 0.05;
382
265
  ay += Math.sin(p.j * 1.3) * 0.05;
383
-
384
- // 속도와 위치 업데이트
385
266
  p.vx = (p.vx + ax) * Math.random();
386
267
  p.vy = (p.vy + ay) * Math.random();
387
268
  p.x += p.vx;
388
269
  p.y += p.vy;
389
270
  }
390
-
391
- // 파티클 그리기
392
271
  this.ctx.fillStyle = this.config.particleColor;
393
272
  const r = this.config.pointSize * this.DPR;
394
273
  for (const p of this.particles) {
@@ -396,85 +275,70 @@ export class MasonEffect {
396
275
  this.ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
397
276
  this.ctx.fill();
398
277
  }
399
-
400
278
  if (this.config.onUpdate) {
401
279
  this.config.onUpdate(this);
402
280
  }
403
281
  }
404
-
405
- animate(): void {
282
+ animate() {
406
283
  if (!this.isRunning) return;
407
284
  this.update();
408
285
  this.animationId = requestAnimationFrame(() => this.animate());
409
286
  }
410
-
411
- start(): void {
287
+ start() {
412
288
  if (this.isRunning) return;
413
289
  this.isRunning = true;
414
290
  this.animate();
415
291
  }
416
-
417
- stop(): void {
292
+ stop() {
418
293
  this.isRunning = false;
419
294
  if (this.animationId) {
420
295
  cancelAnimationFrame(this.animationId);
421
296
  this.animationId = null;
422
297
  }
423
298
  }
424
-
425
- setupEventListeners(): void {
426
- window.addEventListener('resize', this.handleResize);
427
- this.canvas.addEventListener('mousemove', this.handleMouseMove);
428
- this.canvas.addEventListener('mouseleave', this.handleMouseLeave);
429
- this.canvas.addEventListener('mousedown', this.handleMouseDown);
430
- window.addEventListener('mouseup', this.handleMouseUp);
299
+ setupEventListeners() {
300
+ window.addEventListener("resize", this.handleResize);
301
+ this.canvas.addEventListener("mousemove", this.handleMouseMove);
302
+ this.canvas.addEventListener("mouseleave", this.handleMouseLeave);
303
+ this.canvas.addEventListener("mousedown", this.handleMouseDown);
304
+ window.addEventListener("mouseup", this.handleMouseUp);
431
305
  }
432
-
433
- removeEventListeners(): void {
434
- window.removeEventListener('resize', this.handleResize);
435
- this.canvas.removeEventListener('mousemove', this.handleMouseMove);
436
- this.canvas.removeEventListener('mouseleave', this.handleMouseLeave);
437
- this.canvas.removeEventListener('mousedown', this.handleMouseDown);
438
- window.removeEventListener('mouseup', this.handleMouseUp);
306
+ removeEventListeners() {
307
+ window.removeEventListener("resize", this.handleResize);
308
+ this.canvas.removeEventListener("mousemove", this.handleMouseMove);
309
+ this.canvas.removeEventListener("mouseleave", this.handleMouseLeave);
310
+ this.canvas.removeEventListener("mousedown", this.handleMouseDown);
311
+ window.removeEventListener("mouseup", this.handleMouseUp);
439
312
  }
440
-
441
- handleResize(): void {
313
+ handleResize() {
442
314
  this.resize();
443
315
  }
444
-
445
- handleMouseMove(e: MouseEvent): void {
316
+ handleMouseMove(e) {
446
317
  const rect = this.canvas.getBoundingClientRect();
447
318
  this.mouse.x = (e.clientX - rect.left) * this.DPR;
448
319
  this.mouse.y = (e.clientY - rect.top) * this.DPR;
449
320
  }
450
-
451
- handleMouseLeave(): void {
321
+ handleMouseLeave() {
452
322
  this.mouse.x = this.mouse.y = 0;
453
323
  }
454
-
455
- handleMouseDown(): void {
324
+ handleMouseDown() {
456
325
  this.mouse.down = true;
457
326
  }
458
-
459
- handleMouseUp(): void {
327
+ handleMouseUp() {
460
328
  this.mouse.down = false;
461
329
  }
462
-
463
330
  // 설정 업데이트
464
- updateConfig(newConfig: Partial<MasonEffectOptions>): void {
465
- // 디바운스 적용
331
+ updateConfig(newConfig) {
466
332
  this._debouncedUpdateConfig(newConfig);
467
333
  }
468
-
469
- _updateConfigInternal(newConfig: Partial<MasonEffectOptions>): void {
334
+ _updateConfigInternal(newConfig) {
470
335
  this.config = { ...this.config, ...newConfig };
471
336
  if (newConfig.text) {
472
337
  this.buildTargets();
473
338
  }
474
339
  }
475
-
476
340
  // 파괴 및 정리
477
- destroy(): void {
341
+ destroy() {
478
342
  this.stop();
479
343
  this.removeEventListeners();
480
344
  if (this.intersectionObserver) {
@@ -486,7 +350,133 @@ export class MasonEffect {
486
350
  }
487
351
  }
488
352
  }
489
-
490
- // 기본 export
491
- export default MasonEffect;
492
-
353
+ const _sfc_main = /* @__PURE__ */ defineComponent({
354
+ __name: "MasonEffect",
355
+ props: {
356
+ className: { default: "" },
357
+ style: { default: () => ({}) },
358
+ text: { default: "mason effect" },
359
+ densityStep: { default: 2 },
360
+ maxParticles: { default: 3200 },
361
+ pointSize: { default: 0.5 },
362
+ ease: { default: 0.05 },
363
+ repelRadius: { default: 150 },
364
+ repelStrength: { default: 1 },
365
+ particleColor: { default: "#fff" },
366
+ fontFamily: { default: "Inter, system-ui, Arial" },
367
+ fontSize: { default: null },
368
+ width: { default: null },
369
+ height: { default: null },
370
+ devicePixelRatio: { default: null },
371
+ debounceDelay: {},
372
+ onReady: { type: Function, default: null },
373
+ onUpdate: { type: Function, default: null }
374
+ },
375
+ emits: ["ready", "update"],
376
+ setup(__props, { expose: __expose, emit: __emit }) {
377
+ const props = __props;
378
+ const emit = __emit;
379
+ const container = ref(null);
380
+ let instance = null;
381
+ const init = () => {
382
+ if (!container.value) return;
383
+ const options = {
384
+ text: props.text,
385
+ densityStep: props.densityStep,
386
+ maxParticles: props.maxParticles,
387
+ pointSize: props.pointSize,
388
+ ease: props.ease,
389
+ repelRadius: props.repelRadius,
390
+ repelStrength: props.repelStrength,
391
+ particleColor: props.particleColor,
392
+ fontFamily: props.fontFamily,
393
+ fontSize: props.fontSize,
394
+ width: props.width,
395
+ height: props.height,
396
+ devicePixelRatio: props.devicePixelRatio,
397
+ onReady: (inst) => {
398
+ if (props.onReady) props.onReady(inst);
399
+ emit("ready", inst);
400
+ },
401
+ onUpdate: (inst) => {
402
+ if (props.onUpdate) props.onUpdate(inst);
403
+ emit("update", inst);
404
+ }
405
+ };
406
+ instance = new MasonEffect(container.value, options);
407
+ };
408
+ watch(
409
+ () => [
410
+ props.text,
411
+ props.densityStep,
412
+ props.maxParticles,
413
+ props.pointSize,
414
+ props.ease,
415
+ props.repelRadius,
416
+ props.repelStrength,
417
+ props.particleColor,
418
+ props.fontFamily,
419
+ props.fontSize,
420
+ props.width,
421
+ props.height,
422
+ props.devicePixelRatio
423
+ ],
424
+ () => {
425
+ if (instance) {
426
+ instance.updateConfig({
427
+ text: props.text,
428
+ densityStep: props.densityStep,
429
+ maxParticles: props.maxParticles,
430
+ pointSize: props.pointSize,
431
+ ease: props.ease,
432
+ repelRadius: props.repelRadius,
433
+ repelStrength: props.repelStrength,
434
+ particleColor: props.particleColor,
435
+ fontFamily: props.fontFamily,
436
+ fontSize: props.fontSize,
437
+ width: props.width,
438
+ height: props.height,
439
+ devicePixelRatio: props.devicePixelRatio
440
+ });
441
+ }
442
+ }
443
+ );
444
+ onMounted(() => {
445
+ init();
446
+ });
447
+ onBeforeUnmount(() => {
448
+ if (instance) {
449
+ instance.destroy();
450
+ instance = null;
451
+ }
452
+ });
453
+ __expose({
454
+ morph: (textOrOptions) => {
455
+ if (instance) instance.morph(textOrOptions);
456
+ },
457
+ scatter: () => {
458
+ if (instance) instance.scatter();
459
+ },
460
+ updateConfig: (config) => {
461
+ if (instance) instance.updateConfig(config);
462
+ },
463
+ destroy: () => {
464
+ if (instance) {
465
+ instance.destroy();
466
+ instance = null;
467
+ }
468
+ }
469
+ });
470
+ return (_ctx, _cache) => {
471
+ return openBlock(), createElementBlock("div", {
472
+ ref_key: "container",
473
+ ref: container,
474
+ class: normalizeClass(__props.className),
475
+ style: normalizeStyle(__props.style)
476
+ }, null, 6);
477
+ };
478
+ }
479
+ });
480
+ export {
481
+ _sfc_main as default
482
+ };