masoneffect 0.1.19 → 0.1.21

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.
@@ -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,27 +100,20 @@ 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
-
204
- // 최소 크기 보장
205
112
  if (width <= 0 || height <= 0) {
206
113
  return;
207
114
  }
208
-
209
115
  this.W = Math.floor(width * this.DPR);
210
116
  this.H = Math.floor(height * this.DPR);
211
-
212
- // 캔버스 크기 제한 (메모리 오류 방지)
213
- // getImageData는 최대 약 268MB (4096x4096x4)까지 지원
214
117
  const MAX_CANVAS_SIZE = 4096;
215
118
  if (this.W > MAX_CANVAS_SIZE || this.H > MAX_CANVAS_SIZE) {
216
119
  const scale = Math.min(MAX_CANVAS_SIZE / this.W, MAX_CANVAS_SIZE / this.H);
@@ -218,13 +121,10 @@ export class MasonEffect {
218
121
  this.H = Math.floor(this.H * scale);
219
122
  this.DPR = this.DPR * scale;
220
123
  }
221
-
222
124
  this.canvas.width = this.W;
223
125
  this.canvas.height = this.H;
224
- this.canvas.style.width = width + 'px';
225
- this.canvas.style.height = height + 'px';
226
-
227
- // 크기가 유효할 때만 buildTargets 실행
126
+ this.canvas.style.width = width + "px";
127
+ this.canvas.style.height = height + "px";
228
128
  if (this.W > 0 && this.H > 0) {
229
129
  this.buildTargets();
230
130
  if (!this.particles.length) {
@@ -232,56 +132,42 @@ export class MasonEffect {
232
132
  }
233
133
  }
234
134
  }
235
-
236
- buildTargets(): void {
237
- // 크기 검증
135
+ buildTargets() {
238
136
  if (this.W <= 0 || this.H <= 0) {
239
137
  return;
240
138
  }
241
-
242
139
  const text = this.config.text;
243
140
  this.offCanvas.width = this.W;
244
141
  this.offCanvas.height = this.H;
245
142
  this.offCtx.clearRect(0, 0, this.offCanvas.width, this.offCanvas.height);
246
-
247
143
  const base = Math.min(this.W, this.H);
248
144
  const fontSize = this.config.fontSize || Math.max(80, Math.floor(base * 0.18));
249
- this.offCtx.fillStyle = '#ffffff';
250
- this.offCtx.textAlign = 'center';
251
- this.offCtx.textBaseline = 'middle';
145
+ this.offCtx.fillStyle = "#ffffff";
146
+ this.offCtx.textAlign = "center";
147
+ this.offCtx.textBaseline = "middle";
252
148
  this.offCtx.font = `400 ${fontSize}px ${this.config.fontFamily}`;
253
-
254
- // 글자 간격 계산 및 그리기
255
- const chars = text.split('');
149
+ const chars = text.split("");
256
150
  const spacing = fontSize * 0.05;
257
151
  const totalWidth = this.offCtx.measureText(text).width + spacing * (chars.length - 1);
258
152
  let x = this.W / 2 - totalWidth / 2;
259
-
260
153
  for (const ch of chars) {
261
154
  this.offCtx.fillText(ch, x + this.offCtx.measureText(ch).width / 2, this.H / 2);
262
155
  x += this.offCtx.measureText(ch).width + spacing;
263
156
  }
264
-
265
- // 픽셀 샘플링
266
157
  const step = Math.max(2, this.config.densityStep);
267
158
  const img = this.offCtx.getImageData(0, 0, this.W, this.H).data;
268
- const targets: Array<{ x: number; y: number }> = [];
269
-
159
+ const targets = [];
270
160
  for (let y = 0; y < this.H; y += step) {
271
- for (let x = 0; x < this.W; x += step) {
272
- 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;
273
163
  if (img[i] + img[i + 1] + img[i + 2] > 600) {
274
- targets.push({ x, y });
164
+ targets.push({ x: x2, y });
275
165
  }
276
166
  }
277
167
  }
278
-
279
- // 파티클 수 제한
280
168
  while (targets.length > this.config.maxParticles) {
281
169
  targets.splice(Math.floor(Math.random() * targets.length), 1);
282
170
  }
283
-
284
- // 파티클 수 조정
285
171
  if (this.particles.length < targets.length) {
286
172
  const need = targets.length - this.particles.length;
287
173
  for (let i = 0; i < need; i++) {
@@ -290,8 +176,6 @@ export class MasonEffect {
290
176
  } else if (this.particles.length > targets.length) {
291
177
  this.particles.length = targets.length;
292
178
  }
293
-
294
- // 목표 좌표 할당
295
179
  for (let i = 0; i < this.particles.length; i++) {
296
180
  const p = this.particles[i];
297
181
  const t = targets[i];
@@ -299,9 +183,7 @@ export class MasonEffect {
299
183
  p.ty = t.y;
300
184
  }
301
185
  }
302
-
303
- makeParticle(): Particle {
304
- // 캔버스 전체에 골고루 분포 (여백 없이)
186
+ makeParticle() {
305
187
  const sx = Math.random() * this.W;
306
188
  const sy = Math.random() * this.H;
307
189
  return {
@@ -311,35 +193,29 @@ export class MasonEffect {
311
193
  vy: 0,
312
194
  tx: sx,
313
195
  ty: sy,
314
- initialX: sx, // 초기 위치 저장 (scatter 시 돌아갈 위치)
196
+ initialX: sx,
197
+ // 초기 위치 저장 (scatter 시 돌아갈 위치)
315
198
  initialY: sy,
316
- j: Math.random() * Math.PI * 2,
199
+ j: Math.random() * Math.PI * 2
317
200
  };
318
201
  }
319
-
320
- initParticles(): void {
321
- // 캔버스 전체에 골고루 분포 (여백 없이)
202
+ initParticles() {
322
203
  for (const p of this.particles) {
323
204
  const sx = Math.random() * this.W;
324
205
  const sy = Math.random() * this.H;
325
206
  p.x = sx;
326
207
  p.y = sy;
327
208
  p.vx = p.vy = 0;
328
- // 초기 위치 저장 (scatter 시 돌아갈 위치)
329
209
  p.initialX = sx;
330
210
  p.initialY = sy;
331
211
  }
332
212
  }
333
-
334
- scatter(): void {
335
- // 각 파티클을 초기 위치로 돌아가도록 설정
213
+ scatter() {
336
214
  for (const p of this.particles) {
337
- // 초기 위치가 저장되어 있으면 위치로, 없으면 현재 위치 유지
338
- if (p.initialX !== undefined && p.initialY !== undefined) {
215
+ if (p.initialX !== void 0 && p.initialY !== void 0) {
339
216
  p.tx = p.initialX;
340
217
  p.ty = p.initialY;
341
218
  } else {
342
- // 초기 위치가 없으면 현재 위치를 초기 위치로 저장
343
219
  p.initialX = p.x;
344
220
  p.initialY = p.y;
345
221
  p.tx = p.initialX;
@@ -347,71 +223,51 @@ export class MasonEffect {
347
223
  }
348
224
  }
349
225
  }
350
-
351
- morph(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
352
- // 즉시 실행이 필요한 경우 (예: 초기화 시)를 위해 내부 메서드 직접 호출
353
- // 일반적인 경우에는 디바운스 적용
226
+ morph(textOrOptions) {
354
227
  this._debouncedMorph(textOrOptions);
355
228
  }
356
-
357
- _morphInternal(textOrOptions?: string | Partial<MasonEffectOptions> | null): void {
358
- // W와 H가 0이면 resize 먼저 실행
229
+ _morphInternal(textOrOptions) {
359
230
  if (this.W === 0 || this.H === 0) {
360
231
  this.resize();
361
232
  }
362
-
363
- if (typeof textOrOptions === 'string') {
364
- // 문자열인 경우: 기존 동작 유지
233
+ if (typeof textOrOptions === "string") {
365
234
  this.config.text = textOrOptions;
366
235
  this.buildTargets();
367
- } else if (textOrOptions && typeof textOrOptions === 'object') {
368
- // 객체인 경우: 텍스트와 함께 다른 설정도 변경
369
- const needsRebuild = textOrOptions.text !== undefined;
236
+ } else if (textOrOptions && typeof textOrOptions === "object") {
237
+ const needsRebuild = textOrOptions.text !== void 0;
370
238
  this.config = { ...this.config, ...textOrOptions };
371
239
  if (needsRebuild) {
372
240
  this.buildTargets();
373
241
  }
374
242
  } else {
375
- // null이거나 undefined인 경우: 현재 텍스트로 재빌드
376
243
  this.buildTargets();
377
244
  }
378
245
  }
379
-
380
- update(): void {
246
+ update() {
381
247
  this.ctx.clearRect(0, 0, this.W, this.H);
382
-
383
248
  for (const p of this.particles) {
384
- // 목표 좌표로 당기는 힘
385
249
  let ax = (p.tx - p.x) * this.config.ease;
386
250
  let ay = (p.ty - p.y) * this.config.ease;
387
-
388
- // 마우스 반발/흡입
389
251
  if (this.mouse.x || this.mouse.y) {
390
252
  const dx = p.x - this.mouse.x;
391
253
  const dy = p.y - this.mouse.y;
392
254
  const d2 = dx * dx + dy * dy;
393
- const r = this.config.repelRadius * this.DPR;
394
- if (d2 < r * r) {
395
- const d = Math.sqrt(d2) + 0.0001;
396
- const f = (this.mouse.down ? -1 : 1) * this.config.repelStrength * (1 - d / r);
397
- ax += (dx / d) * f * 6.0;
398
- 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;
399
261
  }
400
262
  }
401
-
402
- // 진동 효과
403
263
  p.j += 2;
404
264
  ax += Math.cos(p.j) * 0.05;
405
265
  ay += Math.sin(p.j * 1.3) * 0.05;
406
-
407
- // 속도와 위치 업데이트
408
266
  p.vx = (p.vx + ax) * Math.random();
409
267
  p.vy = (p.vy + ay) * Math.random();
410
268
  p.x += p.vx;
411
269
  p.y += p.vy;
412
270
  }
413
-
414
- // 파티클 그리기
415
271
  this.ctx.fillStyle = this.config.particleColor;
416
272
  const r = this.config.pointSize * this.DPR;
417
273
  for (const p of this.particles) {
@@ -419,85 +275,70 @@ export class MasonEffect {
419
275
  this.ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
420
276
  this.ctx.fill();
421
277
  }
422
-
423
278
  if (this.config.onUpdate) {
424
279
  this.config.onUpdate(this);
425
280
  }
426
281
  }
427
-
428
- animate(): void {
282
+ animate() {
429
283
  if (!this.isRunning) return;
430
284
  this.update();
431
285
  this.animationId = requestAnimationFrame(() => this.animate());
432
286
  }
433
-
434
- start(): void {
287
+ start() {
435
288
  if (this.isRunning) return;
436
289
  this.isRunning = true;
437
290
  this.animate();
438
291
  }
439
-
440
- stop(): void {
292
+ stop() {
441
293
  this.isRunning = false;
442
294
  if (this.animationId) {
443
295
  cancelAnimationFrame(this.animationId);
444
296
  this.animationId = null;
445
297
  }
446
298
  }
447
-
448
- setupEventListeners(): void {
449
- window.addEventListener('resize', this.handleResize);
450
- this.canvas.addEventListener('mousemove', this.handleMouseMove);
451
- this.canvas.addEventListener('mouseleave', this.handleMouseLeave);
452
- this.canvas.addEventListener('mousedown', this.handleMouseDown);
453
- 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);
454
305
  }
455
-
456
- removeEventListeners(): void {
457
- window.removeEventListener('resize', this.handleResize);
458
- this.canvas.removeEventListener('mousemove', this.handleMouseMove);
459
- this.canvas.removeEventListener('mouseleave', this.handleMouseLeave);
460
- this.canvas.removeEventListener('mousedown', this.handleMouseDown);
461
- 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);
462
312
  }
463
-
464
- handleResize(): void {
313
+ handleResize() {
465
314
  this.resize();
466
315
  }
467
-
468
- handleMouseMove(e: MouseEvent): void {
316
+ handleMouseMove(e) {
469
317
  const rect = this.canvas.getBoundingClientRect();
470
318
  this.mouse.x = (e.clientX - rect.left) * this.DPR;
471
319
  this.mouse.y = (e.clientY - rect.top) * this.DPR;
472
320
  }
473
-
474
- handleMouseLeave(): void {
321
+ handleMouseLeave() {
475
322
  this.mouse.x = this.mouse.y = 0;
476
323
  }
477
-
478
- handleMouseDown(): void {
324
+ handleMouseDown() {
479
325
  this.mouse.down = true;
480
326
  }
481
-
482
- handleMouseUp(): void {
327
+ handleMouseUp() {
483
328
  this.mouse.down = false;
484
329
  }
485
-
486
330
  // 설정 업데이트
487
- updateConfig(newConfig: Partial<MasonEffectOptions>): void {
488
- // 디바운스 적용
331
+ updateConfig(newConfig) {
489
332
  this._debouncedUpdateConfig(newConfig);
490
333
  }
491
-
492
- _updateConfigInternal(newConfig: Partial<MasonEffectOptions>): void {
334
+ _updateConfigInternal(newConfig) {
493
335
  this.config = { ...this.config, ...newConfig };
494
336
  if (newConfig.text) {
495
337
  this.buildTargets();
496
338
  }
497
339
  }
498
-
499
340
  // 파괴 및 정리
500
- destroy(): void {
341
+ destroy() {
501
342
  this.stop();
502
343
  this.removeEventListeners();
503
344
  if (this.intersectionObserver) {
@@ -509,7 +350,133 @@ export class MasonEffect {
509
350
  }
510
351
  }
511
352
  }
512
-
513
- // 기본 export
514
- export default MasonEffect;
515
-
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
+ };