masoneffect 1.0.29 → 2.0.1

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 (198) hide show
  1. package/README.md +320 -231
  2. package/dist/core/count/index.d.ts +52 -0
  3. package/dist/core/count/index.d.ts.map +1 -0
  4. package/dist/core/index.d.ts +15 -97
  5. package/dist/core/index.d.ts.map +1 -1
  6. package/dist/core/textToParticle/index.d.ts +103 -0
  7. package/dist/core/textToParticle/index.d.ts.map +1 -0
  8. package/dist/count/core/count/index.d.ts +52 -0
  9. package/dist/count/core/count/index.d.ts.map +1 -0
  10. package/dist/count/core/index.d.ts +18 -0
  11. package/dist/count/core/index.d.ts.map +1 -0
  12. package/dist/count/core/textToParticle/index.d.ts +103 -0
  13. package/dist/count/core/textToParticle/index.d.ts.map +1 -0
  14. package/dist/count/index.cjs +1 -0
  15. package/dist/count/index.d.ts +16 -0
  16. package/dist/count/index.d.ts.map +1 -0
  17. package/dist/count/index.mjs +1 -0
  18. package/dist/count/index.umd.d.ts +7 -0
  19. package/dist/count/index.umd.d.ts.map +1 -0
  20. package/dist/count/react/MasonEffect.d.ts +32 -0
  21. package/dist/count/react/MasonEffect.d.ts.map +1 -0
  22. package/dist/count/react/count/Count.d.ts +19 -0
  23. package/dist/count/react/count/Count.d.ts.map +1 -0
  24. package/dist/count/react/count/index.d.ts +5 -0
  25. package/dist/count/react/count/index.d.ts.map +1 -0
  26. package/dist/count/react/index.d.ts +12 -0
  27. package/dist/count/react/index.d.ts.map +1 -0
  28. package/dist/count/react/textToParticle/TextToParticle.d.ts +15 -0
  29. package/dist/count/react/textToParticle/TextToParticle.d.ts.map +1 -0
  30. package/dist/count/react/textToParticle/index.d.ts +4 -0
  31. package/dist/count/react/textToParticle/index.d.ts.map +1 -0
  32. package/dist/count/svelte/count/index.d.ts +2 -0
  33. package/dist/count/svelte/count/index.d.ts.map +1 -0
  34. package/dist/count/svelte/index.d.ts +8 -0
  35. package/dist/count/svelte/index.d.ts.map +1 -0
  36. package/dist/count/svelte/textToParticle/index.d.ts +2 -0
  37. package/dist/count/svelte/textToParticle/index.d.ts.map +1 -0
  38. package/dist/count/vue/count/index.d.ts +2 -0
  39. package/dist/count/vue/count/index.d.ts.map +1 -0
  40. package/dist/count/vue/index.d.ts +8 -0
  41. package/dist/count/vue/index.d.ts.map +1 -0
  42. package/dist/count/vue/textToParticle/index.d.ts +2 -0
  43. package/dist/count/vue/textToParticle/index.d.ts.map +1 -0
  44. package/dist/index.cjs +1 -1
  45. package/dist/index.d.ts +12 -4
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.mjs +1 -1
  48. package/dist/index.umd.min.js +1 -1
  49. package/dist/react/MasonEffect.cjs +1 -1
  50. package/dist/react/MasonEffect.mjs +1 -1
  51. package/dist/react/core/count/index.d.ts +51 -0
  52. package/dist/react/core/index.d.ts +15 -97
  53. package/dist/react/core/textToParticle/index.d.ts +102 -0
  54. package/dist/react/count/Count.d.ts +19 -0
  55. package/dist/react/count/Count.d.ts.map +1 -0
  56. package/dist/react/count/core/count/index.d.ts +52 -0
  57. package/dist/react/count/core/count/index.d.ts.map +1 -0
  58. package/dist/react/count/core/index.d.ts +18 -0
  59. package/dist/react/count/core/index.d.ts.map +1 -0
  60. package/dist/react/count/core/textToParticle/index.d.ts +103 -0
  61. package/dist/react/count/core/textToParticle/index.d.ts.map +1 -0
  62. package/dist/react/count/index.cjs +1 -0
  63. package/dist/react/count/index.d.ts +16 -0
  64. package/dist/react/count/index.d.ts.map +1 -0
  65. package/dist/react/count/index.mjs +1 -0
  66. package/dist/react/count/index.umd.d.ts +7 -0
  67. package/dist/react/count/index.umd.d.ts.map +1 -0
  68. package/dist/react/count/react/MasonEffect.d.ts +32 -0
  69. package/dist/react/count/react/MasonEffect.d.ts.map +1 -0
  70. package/dist/react/count/react/count/Count.d.ts +19 -0
  71. package/dist/react/count/react/count/Count.d.ts.map +1 -0
  72. package/dist/react/count/react/count/index.d.ts +5 -0
  73. package/dist/react/count/react/count/index.d.ts.map +1 -0
  74. package/dist/react/count/react/index.d.ts +12 -0
  75. package/dist/react/count/react/index.d.ts.map +1 -0
  76. package/dist/react/count/react/textToParticle/TextToParticle.d.ts +15 -0
  77. package/dist/react/count/react/textToParticle/TextToParticle.d.ts.map +1 -0
  78. package/dist/react/count/react/textToParticle/index.d.ts +4 -0
  79. package/dist/react/count/react/textToParticle/index.d.ts.map +1 -0
  80. package/dist/react/count/svelte/count/index.d.ts +2 -0
  81. package/dist/react/count/svelte/count/index.d.ts.map +1 -0
  82. package/dist/react/count/svelte/index.d.ts +8 -0
  83. package/dist/react/count/svelte/index.d.ts.map +1 -0
  84. package/dist/react/count/svelte/textToParticle/index.d.ts +2 -0
  85. package/dist/react/count/svelte/textToParticle/index.d.ts.map +1 -0
  86. package/dist/react/count/vue/count/index.d.ts +2 -0
  87. package/dist/react/count/vue/count/index.d.ts.map +1 -0
  88. package/dist/react/count/vue/index.d.ts +8 -0
  89. package/dist/react/count/vue/index.d.ts.map +1 -0
  90. package/dist/react/count/vue/textToParticle/index.d.ts +2 -0
  91. package/dist/react/count/vue/textToParticle/index.d.ts.map +1 -0
  92. package/dist/react/index.cjs +12 -2
  93. package/dist/react/index.d.ts.map +1 -1
  94. package/dist/react/index.mjs +2 -1
  95. package/dist/react/react/count/Count.d.ts +18 -0
  96. package/dist/react/react/count/index.d.ts +4 -0
  97. package/dist/react/react/index.d.ts +11 -2
  98. package/dist/react/react/textToParticle/TextToParticle.d.ts +14 -0
  99. package/dist/react/react/textToParticle/index.d.ts +3 -0
  100. package/dist/react/svelte/count/index.d.ts +1 -0
  101. package/dist/react/svelte/index.d.ts +5 -3
  102. package/dist/react/svelte/textToParticle/index.d.ts +1 -0
  103. package/dist/react/textToParticle/TextToParticle.d.ts +15 -0
  104. package/dist/react/textToParticle/TextToParticle.d.ts.map +1 -0
  105. package/dist/react/textToParticle/core/count/index.d.ts +52 -0
  106. package/dist/react/textToParticle/core/count/index.d.ts.map +1 -0
  107. package/dist/react/textToParticle/core/index.d.ts +18 -0
  108. package/dist/react/textToParticle/core/index.d.ts.map +1 -0
  109. package/dist/react/textToParticle/core/textToParticle/index.d.ts +103 -0
  110. package/dist/react/textToParticle/core/textToParticle/index.d.ts.map +1 -0
  111. package/dist/react/textToParticle/index.cjs +1 -0
  112. package/dist/react/textToParticle/index.d.ts +16 -0
  113. package/dist/react/textToParticle/index.d.ts.map +1 -0
  114. package/dist/react/textToParticle/index.mjs +1 -0
  115. package/dist/react/textToParticle/index.umd.d.ts +7 -0
  116. package/dist/react/textToParticle/index.umd.d.ts.map +1 -0
  117. package/dist/react/textToParticle/react/MasonEffect.d.ts +32 -0
  118. package/dist/react/textToParticle/react/MasonEffect.d.ts.map +1 -0
  119. package/dist/react/textToParticle/react/count/Count.d.ts +19 -0
  120. package/dist/react/textToParticle/react/count/Count.d.ts.map +1 -0
  121. package/dist/react/textToParticle/react/count/index.d.ts +5 -0
  122. package/dist/react/textToParticle/react/count/index.d.ts.map +1 -0
  123. package/dist/react/textToParticle/react/index.d.ts +12 -0
  124. package/dist/react/textToParticle/react/index.d.ts.map +1 -0
  125. package/dist/react/textToParticle/react/textToParticle/TextToParticle.d.ts +15 -0
  126. package/dist/react/textToParticle/react/textToParticle/TextToParticle.d.ts.map +1 -0
  127. package/dist/react/textToParticle/react/textToParticle/index.d.ts +4 -0
  128. package/dist/react/textToParticle/react/textToParticle/index.d.ts.map +1 -0
  129. package/dist/react/textToParticle/svelte/count/index.d.ts +2 -0
  130. package/dist/react/textToParticle/svelte/count/index.d.ts.map +1 -0
  131. package/dist/react/textToParticle/svelte/index.d.ts +8 -0
  132. package/dist/react/textToParticle/svelte/index.d.ts.map +1 -0
  133. package/dist/react/textToParticle/svelte/textToParticle/index.d.ts +2 -0
  134. package/dist/react/textToParticle/svelte/textToParticle/index.d.ts.map +1 -0
  135. package/dist/react/textToParticle/vue/count/index.d.ts +2 -0
  136. package/dist/react/textToParticle/vue/count/index.d.ts.map +1 -0
  137. package/dist/react/textToParticle/vue/index.d.ts +8 -0
  138. package/dist/react/textToParticle/vue/index.d.ts.map +1 -0
  139. package/dist/react/textToParticle/vue/textToParticle/index.d.ts +2 -0
  140. package/dist/react/textToParticle/vue/textToParticle/index.d.ts.map +1 -0
  141. package/dist/react/vue/count/index.d.ts +1 -0
  142. package/dist/react/vue/index.d.ts +5 -3
  143. package/dist/react/vue/textToParticle/index.d.ts +1 -0
  144. package/dist/svelte/count/index.cjs +1 -0
  145. package/dist/svelte/count/index.d.ts +163 -0
  146. package/dist/svelte/count/index.mjs +659 -0
  147. package/dist/svelte/index.cjs +1 -1
  148. package/dist/svelte/index.d.ts +88 -29
  149. package/dist/svelte/index.mjs +404 -10
  150. package/dist/svelte/textToParticle/index.cjs +1 -0
  151. package/dist/svelte/textToParticle/index.d.ts +163 -0
  152. package/dist/svelte/textToParticle/index.mjs +924 -0
  153. package/dist/textToParticle/core/count/index.d.ts +52 -0
  154. package/dist/textToParticle/core/count/index.d.ts.map +1 -0
  155. package/dist/textToParticle/core/index.d.ts +18 -0
  156. package/dist/textToParticle/core/index.d.ts.map +1 -0
  157. package/dist/textToParticle/core/textToParticle/index.d.ts +103 -0
  158. package/dist/textToParticle/core/textToParticle/index.d.ts.map +1 -0
  159. package/dist/textToParticle/index.cjs +1 -0
  160. package/dist/textToParticle/index.d.ts +16 -0
  161. package/dist/textToParticle/index.d.ts.map +1 -0
  162. package/dist/textToParticle/index.mjs +1 -0
  163. package/dist/textToParticle/index.umd.d.ts +7 -0
  164. package/dist/textToParticle/index.umd.d.ts.map +1 -0
  165. package/dist/textToParticle/react/MasonEffect.d.ts +32 -0
  166. package/dist/textToParticle/react/MasonEffect.d.ts.map +1 -0
  167. package/dist/textToParticle/react/count/Count.d.ts +19 -0
  168. package/dist/textToParticle/react/count/Count.d.ts.map +1 -0
  169. package/dist/textToParticle/react/count/index.d.ts +5 -0
  170. package/dist/textToParticle/react/count/index.d.ts.map +1 -0
  171. package/dist/textToParticle/react/index.d.ts +12 -0
  172. package/dist/textToParticle/react/index.d.ts.map +1 -0
  173. package/dist/textToParticle/react/textToParticle/TextToParticle.d.ts +15 -0
  174. package/dist/textToParticle/react/textToParticle/TextToParticle.d.ts.map +1 -0
  175. package/dist/textToParticle/react/textToParticle/index.d.ts +4 -0
  176. package/dist/textToParticle/react/textToParticle/index.d.ts.map +1 -0
  177. package/dist/textToParticle/svelte/count/index.d.ts +2 -0
  178. package/dist/textToParticle/svelte/count/index.d.ts.map +1 -0
  179. package/dist/textToParticle/svelte/index.d.ts +8 -0
  180. package/dist/textToParticle/svelte/index.d.ts.map +1 -0
  181. package/dist/textToParticle/svelte/textToParticle/index.d.ts +2 -0
  182. package/dist/textToParticle/svelte/textToParticle/index.d.ts.map +1 -0
  183. package/dist/textToParticle/vue/count/index.d.ts +2 -0
  184. package/dist/textToParticle/vue/count/index.d.ts.map +1 -0
  185. package/dist/textToParticle/vue/index.d.ts +8 -0
  186. package/dist/textToParticle/vue/index.d.ts.map +1 -0
  187. package/dist/textToParticle/vue/textToParticle/index.d.ts +2 -0
  188. package/dist/textToParticle/vue/textToParticle/index.d.ts.map +1 -0
  189. package/dist/vue/count/index.cjs +1 -0
  190. package/dist/vue/count/index.d.ts +163 -0
  191. package/dist/vue/count/index.mjs +286 -0
  192. package/dist/vue/index.cjs +1 -1
  193. package/dist/vue/index.d.ts +88 -29
  194. package/dist/vue/index.mjs +293 -8
  195. package/dist/vue/textToParticle/index.cjs +1 -0
  196. package/dist/vue/textToParticle/index.d.ts +163 -0
  197. package/dist/vue/textToParticle/index.mjs +554 -0
  198. package/package.json +54 -5
@@ -0,0 +1,554 @@
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) {
5
+ const later = () => {
6
+ timeout = null;
7
+ func.apply(this, args);
8
+ };
9
+ if (timeout !== null) {
10
+ clearTimeout(timeout);
11
+ }
12
+ timeout = setTimeout(later, wait);
13
+ };
14
+ }
15
+ class TextToParticle {
16
+ constructor(container, options = {}) {
17
+ this.container = typeof container === "string" ? document.querySelector(container) : container;
18
+ if (!this.container) {
19
+ throw new Error("Container element not found");
20
+ }
21
+ this.config = {
22
+ text: options.text || "mason effect",
23
+ densityStep: options.densityStep ?? 2,
24
+ maxParticles: options.maxParticles ?? 3200,
25
+ pointSize: options.pointSize ?? 0.5,
26
+ ease: options.ease ?? 0.05,
27
+ repelRadius: options.repelRadius ?? 150,
28
+ repelStrength: options.repelStrength ?? 1,
29
+ particleColor: options.particleColor || "#fff",
30
+ fontFamily: options.fontFamily || "Inter, system-ui, Arial",
31
+ fontSize: options.fontSize || null,
32
+ width: options.width || null,
33
+ height: options.height || null,
34
+ devicePixelRatio: options.devicePixelRatio ?? null,
35
+ onReady: options.onReady || null,
36
+ onUpdate: options.onUpdate || null
37
+ };
38
+ this.canvas = document.createElement("canvas");
39
+ const ctx = this.canvas.getContext("2d", { willReadFrequently: true });
40
+ if (!ctx) {
41
+ throw new Error("Canvas context not available");
42
+ }
43
+ this.ctx = ctx;
44
+ this.container.appendChild(this.canvas);
45
+ this.canvas.style.display = "block";
46
+ this.offCanvas = document.createElement("canvas");
47
+ const offCtx = this.offCanvas.getContext("2d", { willReadFrequently: true });
48
+ if (!offCtx) {
49
+ throw new Error("Offscreen canvas context not available");
50
+ }
51
+ this.offCtx = offCtx;
52
+ this.W = 0;
53
+ this.H = 0;
54
+ this.DPR = this.config.devicePixelRatio || Math.min(window.devicePixelRatio || 1, 1.8);
55
+ this.particles = [];
56
+ this.mouse = { x: 0, y: 0, down: false };
57
+ this.animationId = null;
58
+ this.isRunning = false;
59
+ this.isVisible = false;
60
+ this.intersectionObserver = null;
61
+ this.debounceDelay = options.debounceDelay ?? 150;
62
+ const boundHandleResize = this.handleResize.bind(this);
63
+ this.handleResize = debounce(boundHandleResize, this.debounceDelay);
64
+ this.handleMouseMove = this.handleMouseMove.bind(this);
65
+ this.handleMouseLeave = this.handleMouseLeave.bind(this);
66
+ this.handleMouseDown = this.handleMouseDown.bind(this);
67
+ this.handleMouseUp = this.handleMouseUp.bind(this);
68
+ this._debouncedMorph = debounce(this._morphInternal.bind(this), this.debounceDelay);
69
+ this._debouncedUpdateConfig = debounce(this._updateConfigInternal.bind(this), this.debounceDelay);
70
+ this.init();
71
+ }
72
+ init() {
73
+ this.resize();
74
+ this.setupEventListeners();
75
+ this.setupIntersectionObserver();
76
+ if (this.config.onReady) {
77
+ this.config.onReady(this);
78
+ }
79
+ }
80
+ setupIntersectionObserver() {
81
+ if (typeof window === "undefined" || typeof window.IntersectionObserver === "undefined") {
82
+ this.isVisible = true;
83
+ this.start();
84
+ return;
85
+ }
86
+ if (this.intersectionObserver) {
87
+ return;
88
+ }
89
+ this.intersectionObserver = new IntersectionObserver(
90
+ (entries) => {
91
+ for (const entry of entries) {
92
+ if (entry.target !== this.container) continue;
93
+ if (entry.isIntersecting) {
94
+ this.isVisible = true;
95
+ this.start();
96
+ } else {
97
+ this.isVisible = false;
98
+ this.stop();
99
+ }
100
+ }
101
+ },
102
+ {
103
+ threshold: 0.1
104
+ // 10% 이상 보일 때 동작
105
+ }
106
+ );
107
+ this.intersectionObserver.observe(this.container);
108
+ }
109
+ resize() {
110
+ const width = this.config.width || this.container.clientWidth || window.innerWidth;
111
+ const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;
112
+ if (width <= 0 || height <= 0) {
113
+ return;
114
+ }
115
+ this.W = Math.floor(width * this.DPR);
116
+ this.H = Math.floor(height * this.DPR);
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
+ }
124
+ this.canvas.width = this.W;
125
+ this.canvas.height = this.H;
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
+ }
133
+ }
134
+ }
135
+ /**
136
+ * 텍스트가 영역 안에 들어가는지 확인하는 헬퍼 함수 (줄바꿈 지원)
137
+ * @param fontSize 확인할 폰트 크기
138
+ * @param text 텍스트 (\n으로 줄바꿈 구분)
139
+ * @param maxWidth 최대 너비
140
+ * @param maxHeight 최대 높이
141
+ * @returns { width: number, height: number, fits: boolean }
142
+ */
143
+ measureTextFit(fontSize, text, maxWidth, maxHeight) {
144
+ this.offCtx.font = `400 ${fontSize}px ${this.config.fontFamily}`;
145
+ const lines = text.split("\n");
146
+ const lineHeight = fontSize;
147
+ const lineSpacing = fontSize * 0.1;
148
+ const spacing = fontSize * 0.05;
149
+ let maxLineWidth = 0;
150
+ for (const line of lines) {
151
+ if (line.length === 0) continue;
152
+ const textWidth = this.offCtx.measureText(line).width;
153
+ const totalWidth = textWidth + spacing * (line.length > 0 ? line.length - 1 : 0);
154
+ maxLineWidth = Math.max(maxLineWidth, totalWidth);
155
+ }
156
+ const totalHeight = lines.length > 0 ? lineHeight * lines.length + lineSpacing * (lines.length - 1) : lineHeight;
157
+ return {
158
+ width: maxLineWidth,
159
+ height: totalHeight,
160
+ fits: maxLineWidth <= maxWidth && totalHeight <= maxHeight
161
+ };
162
+ }
163
+ /**
164
+ * 이진 검색을 사용하여 적절한 폰트 크기를 찾는 최적화된 함수
165
+ * 반복 횟수를 O(log n)으로 줄여 성능 개선 (최대 15회 반복, 기존 최대 100회에서 대폭 감소)
166
+ */
167
+ findOptimalFontSize(text, maxWidth, maxHeight, initialFontSize) {
168
+ const minFontSize = 12;
169
+ const initialMeasure = this.measureTextFit(initialFontSize, text, maxWidth, maxHeight);
170
+ if (initialMeasure.fits) {
171
+ return initialFontSize;
172
+ }
173
+ if (initialFontSize <= minFontSize) {
174
+ return minFontSize;
175
+ }
176
+ let low = minFontSize;
177
+ let high = initialFontSize;
178
+ let bestSize = minFontSize;
179
+ while (low <= high) {
180
+ const mid = Math.floor((low + high) / 2);
181
+ const measure = this.measureTextFit(mid, text, maxWidth, maxHeight);
182
+ if (measure.fits) {
183
+ bestSize = mid;
184
+ low = mid + 1;
185
+ } else {
186
+ high = mid - 1;
187
+ }
188
+ }
189
+ return bestSize;
190
+ }
191
+ buildTargets() {
192
+ if (this.W <= 0 || this.H <= 0) {
193
+ return;
194
+ }
195
+ const text = this.config.text;
196
+ this.offCanvas.width = this.W;
197
+ this.offCanvas.height = this.H;
198
+ this.offCtx.clearRect(0, 0, this.offCanvas.width, this.offCanvas.height);
199
+ const base = Math.min(this.W, this.H);
200
+ const initialFontSize = this.config.fontSize || Math.max(80, Math.floor(base * 0.18));
201
+ const padding = 40;
202
+ const maxWidth = this.W - padding * 2;
203
+ const maxHeight = this.H - padding * 2;
204
+ const fontSize = this.findOptimalFontSize(text, maxWidth, maxHeight, initialFontSize);
205
+ this.offCtx.fillStyle = "#ffffff";
206
+ this.offCtx.textAlign = "center";
207
+ this.offCtx.textBaseline = "middle";
208
+ this.offCtx.font = `400 ${fontSize}px ${this.config.fontFamily}`;
209
+ const lines = text.split("\n");
210
+ const lineHeight = fontSize;
211
+ const lineSpacing = fontSize * 0.1;
212
+ const spacing = fontSize * 0.05;
213
+ const totalTextHeight = lines.length > 0 ? lineHeight * lines.length + lineSpacing * (lines.length - 1) : lineHeight;
214
+ let startY = this.H / 2 - totalTextHeight / 2 + lineHeight / 2;
215
+ for (const line of lines) {
216
+ if (line.length === 0) {
217
+ startY += lineHeight + lineSpacing;
218
+ continue;
219
+ }
220
+ const chars = line.split("");
221
+ const totalWidth = this.offCtx.measureText(line).width + spacing * (chars.length > 0 ? chars.length - 1 : 0);
222
+ let x = this.W / 2 - totalWidth / 2;
223
+ for (const ch of chars) {
224
+ this.offCtx.fillText(ch, x + this.offCtx.measureText(ch).width / 2, startY);
225
+ x += this.offCtx.measureText(ch).width + spacing;
226
+ }
227
+ startY += lineHeight + lineSpacing;
228
+ }
229
+ const step = Math.max(2, this.config.densityStep);
230
+ const img = this.offCtx.getImageData(0, 0, this.W, this.H).data;
231
+ const targets = [];
232
+ for (let y = 0; y < this.H; y += step) {
233
+ for (let x = 0; x < this.W; x += step) {
234
+ const i = (y * this.W + x) * 4;
235
+ if (img[i] + img[i + 1] + img[i + 2] > 600) {
236
+ targets.push({ x, y });
237
+ }
238
+ }
239
+ }
240
+ while (targets.length > this.config.maxParticles) {
241
+ targets.splice(Math.floor(Math.random() * targets.length), 1);
242
+ }
243
+ if (this.particles.length < targets.length) {
244
+ const need = targets.length - this.particles.length;
245
+ for (let i = 0; i < need; i++) {
246
+ this.particles.push(this.makeParticle());
247
+ }
248
+ } else if (this.particles.length > targets.length) {
249
+ this.particles.length = targets.length;
250
+ }
251
+ for (let i = 0; i < this.particles.length; i++) {
252
+ const p = this.particles[i];
253
+ const t = targets[i];
254
+ p.tx = t.x;
255
+ p.ty = t.y;
256
+ }
257
+ }
258
+ makeParticle() {
259
+ const sx = Math.random() * this.W;
260
+ const sy = Math.random() * this.H;
261
+ return {
262
+ x: sx,
263
+ y: sy,
264
+ vx: 0,
265
+ vy: 0,
266
+ tx: sx,
267
+ ty: sy,
268
+ initialX: sx,
269
+ // 초기 위치 저장 (scatter 시 돌아갈 위치)
270
+ initialY: sy,
271
+ j: Math.random() * Math.PI * 2
272
+ };
273
+ }
274
+ initParticles() {
275
+ for (const p of this.particles) {
276
+ const sx = Math.random() * this.W;
277
+ const sy = Math.random() * this.H;
278
+ p.x = sx;
279
+ p.y = sy;
280
+ p.vx = p.vy = 0;
281
+ p.initialX = sx;
282
+ p.initialY = sy;
283
+ }
284
+ }
285
+ scatter() {
286
+ for (const p of this.particles) {
287
+ if (p.initialX !== void 0 && p.initialY !== void 0) {
288
+ p.tx = p.initialX;
289
+ p.ty = p.initialY;
290
+ } else {
291
+ p.initialX = p.x;
292
+ p.initialY = p.y;
293
+ p.tx = p.initialX;
294
+ p.ty = p.initialY;
295
+ }
296
+ }
297
+ }
298
+ morph(textOrOptions) {
299
+ this._debouncedMorph(textOrOptions);
300
+ }
301
+ _morphInternal(textOrOptions) {
302
+ if (this.W === 0 || this.H === 0) {
303
+ this.resize();
304
+ }
305
+ if (typeof textOrOptions === "string") {
306
+ this.config.text = textOrOptions;
307
+ this.buildTargets();
308
+ } else if (textOrOptions && typeof textOrOptions === "object") {
309
+ const needsRebuild = textOrOptions.text !== void 0;
310
+ this.config = { ...this.config, ...textOrOptions };
311
+ if (needsRebuild) {
312
+ this.buildTargets();
313
+ }
314
+ } else {
315
+ this.buildTargets();
316
+ }
317
+ }
318
+ update() {
319
+ this.ctx.clearRect(0, 0, this.W, this.H);
320
+ for (const p of this.particles) {
321
+ let ax = (p.tx - p.x) * this.config.ease;
322
+ let ay = (p.ty - p.y) * this.config.ease;
323
+ if (this.mouse.x || this.mouse.y) {
324
+ const dx = p.x - this.mouse.x;
325
+ const dy = p.y - this.mouse.y;
326
+ const d2 = dx * dx + dy * dy;
327
+ const r2 = this.config.repelRadius * this.DPR;
328
+ if (d2 < r2 * r2) {
329
+ const d = Math.sqrt(d2) + 1e-4;
330
+ const f = (this.mouse.down ? -1 : 1) * this.config.repelStrength * (1 - d / r2);
331
+ ax += dx / d * f * 6;
332
+ ay += dy / d * f * 6;
333
+ }
334
+ }
335
+ p.j += 2;
336
+ ax += Math.cos(p.j) * 0.05;
337
+ ay += Math.sin(p.j * 1.3) * 0.05;
338
+ p.vx = (p.vx + ax) * Math.random();
339
+ p.vy = (p.vy + ay) * Math.random();
340
+ p.x += p.vx;
341
+ p.y += p.vy;
342
+ }
343
+ this.ctx.fillStyle = this.config.particleColor;
344
+ const r = this.config.pointSize * this.DPR;
345
+ for (const p of this.particles) {
346
+ this.ctx.beginPath();
347
+ this.ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
348
+ this.ctx.fill();
349
+ }
350
+ if (this.config.onUpdate) {
351
+ this.config.onUpdate(this);
352
+ }
353
+ }
354
+ animate() {
355
+ if (!this.isRunning) return;
356
+ this.update();
357
+ this.animationId = requestAnimationFrame(() => this.animate());
358
+ }
359
+ start() {
360
+ if (this.isRunning) return;
361
+ this.isRunning = true;
362
+ this.animate();
363
+ }
364
+ stop() {
365
+ this.isRunning = false;
366
+ if (this.animationId) {
367
+ cancelAnimationFrame(this.animationId);
368
+ this.animationId = null;
369
+ }
370
+ }
371
+ setupEventListeners() {
372
+ window.addEventListener("resize", this.handleResize);
373
+ this.canvas.addEventListener("mousemove", this.handleMouseMove);
374
+ this.canvas.addEventListener("mouseleave", this.handleMouseLeave);
375
+ this.canvas.addEventListener("mousedown", this.handleMouseDown);
376
+ window.addEventListener("mouseup", this.handleMouseUp);
377
+ }
378
+ removeEventListeners() {
379
+ window.removeEventListener("resize", this.handleResize);
380
+ this.canvas.removeEventListener("mousemove", this.handleMouseMove);
381
+ this.canvas.removeEventListener("mouseleave", this.handleMouseLeave);
382
+ this.canvas.removeEventListener("mousedown", this.handleMouseDown);
383
+ window.removeEventListener("mouseup", this.handleMouseUp);
384
+ }
385
+ handleResize() {
386
+ this.resize();
387
+ }
388
+ handleMouseMove(e) {
389
+ const rect = this.canvas.getBoundingClientRect();
390
+ this.mouse.x = (e.clientX - rect.left) * this.DPR;
391
+ this.mouse.y = (e.clientY - rect.top) * this.DPR;
392
+ }
393
+ handleMouseLeave() {
394
+ this.mouse.x = this.mouse.y = 0;
395
+ }
396
+ handleMouseDown() {
397
+ this.mouse.down = true;
398
+ }
399
+ handleMouseUp() {
400
+ this.mouse.down = false;
401
+ }
402
+ // 설정 업데이트
403
+ updateConfig(newConfig) {
404
+ this._debouncedUpdateConfig(newConfig);
405
+ }
406
+ _updateConfigInternal(newConfig) {
407
+ this.config = { ...this.config, ...newConfig };
408
+ if (newConfig.text) {
409
+ this.buildTargets();
410
+ }
411
+ }
412
+ // 파괴 및 정리
413
+ destroy() {
414
+ this.stop();
415
+ this.removeEventListeners();
416
+ if (this.intersectionObserver) {
417
+ this.intersectionObserver.disconnect();
418
+ this.intersectionObserver = null;
419
+ }
420
+ if (this.canvas && this.canvas.parentNode) {
421
+ this.canvas.parentNode.removeChild(this.canvas);
422
+ }
423
+ }
424
+ }
425
+ const _sfc_main = /* @__PURE__ */ defineComponent({
426
+ __name: "TextToParticle",
427
+ props: {
428
+ className: { default: "" },
429
+ style: { default: () => ({}) },
430
+ text: { default: "mason effect" },
431
+ densityStep: { default: 2 },
432
+ maxParticles: { default: 3200 },
433
+ pointSize: { default: 0.5 },
434
+ ease: { default: 0.05 },
435
+ repelRadius: { default: 150 },
436
+ repelStrength: { default: 1 },
437
+ particleColor: { default: "#fff" },
438
+ fontFamily: { default: "Inter, system-ui, Arial" },
439
+ fontSize: { default: null },
440
+ width: { default: null },
441
+ height: { default: null },
442
+ devicePixelRatio: { default: null },
443
+ debounceDelay: {},
444
+ onReady: { type: Function, default: null },
445
+ onUpdate: { type: Function, default: null }
446
+ },
447
+ emits: ["ready", "update"],
448
+ setup(__props, { expose: __expose, emit: __emit }) {
449
+ const props = __props;
450
+ const emit = __emit;
451
+ const container = ref(null);
452
+ let instance = null;
453
+ const init = () => {
454
+ if (!container.value) return;
455
+ const options = {
456
+ text: props.text,
457
+ densityStep: props.densityStep,
458
+ maxParticles: props.maxParticles,
459
+ pointSize: props.pointSize,
460
+ ease: props.ease,
461
+ repelRadius: props.repelRadius,
462
+ repelStrength: props.repelStrength,
463
+ particleColor: props.particleColor,
464
+ fontFamily: props.fontFamily,
465
+ fontSize: props.fontSize,
466
+ width: props.width,
467
+ height: props.height,
468
+ devicePixelRatio: props.devicePixelRatio,
469
+ onReady: (inst) => {
470
+ if (props.onReady) props.onReady(inst);
471
+ emit("ready", inst);
472
+ },
473
+ onUpdate: (inst) => {
474
+ if (props.onUpdate) props.onUpdate(inst);
475
+ emit("update", inst);
476
+ }
477
+ };
478
+ instance = new TextToParticle(container.value, options);
479
+ };
480
+ watch(
481
+ () => [
482
+ props.text,
483
+ props.densityStep,
484
+ props.maxParticles,
485
+ props.pointSize,
486
+ props.ease,
487
+ props.repelRadius,
488
+ props.repelStrength,
489
+ props.particleColor,
490
+ props.fontFamily,
491
+ props.fontSize,
492
+ props.width,
493
+ props.height,
494
+ props.devicePixelRatio
495
+ ],
496
+ () => {
497
+ if (instance) {
498
+ instance.updateConfig({
499
+ text: props.text,
500
+ densityStep: props.densityStep,
501
+ maxParticles: props.maxParticles,
502
+ pointSize: props.pointSize,
503
+ ease: props.ease,
504
+ repelRadius: props.repelRadius,
505
+ repelStrength: props.repelStrength,
506
+ particleColor: props.particleColor,
507
+ fontFamily: props.fontFamily,
508
+ fontSize: props.fontSize,
509
+ width: props.width,
510
+ height: props.height,
511
+ devicePixelRatio: props.devicePixelRatio
512
+ });
513
+ }
514
+ }
515
+ );
516
+ onMounted(() => {
517
+ init();
518
+ });
519
+ onBeforeUnmount(() => {
520
+ if (instance) {
521
+ instance.destroy();
522
+ instance = null;
523
+ }
524
+ });
525
+ __expose({
526
+ morph: (textOrOptions) => {
527
+ if (instance) instance.morph(textOrOptions);
528
+ },
529
+ scatter: () => {
530
+ if (instance) instance.scatter();
531
+ },
532
+ updateConfig: (config) => {
533
+ if (instance) instance.updateConfig(config);
534
+ },
535
+ destroy: () => {
536
+ if (instance) {
537
+ instance.destroy();
538
+ instance = null;
539
+ }
540
+ }
541
+ });
542
+ return (_ctx, _cache) => {
543
+ return openBlock(), createElementBlock("div", {
544
+ ref_key: "container",
545
+ ref: container,
546
+ class: normalizeClass(__props.className),
547
+ style: normalizeStyle(__props.style)
548
+ }, null, 6);
549
+ };
550
+ }
551
+ });
552
+ export {
553
+ _sfc_main as default
554
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "masoneffect",
3
- "version": "1.0.29",
4
- "description": "A library that provides particle morphing effects. It can be used with React, Vue, Svelte, and vanilla JavaScript.",
3
+ "version": "2.0.1",
4
+ "description": "A collection of animation effects library. Supports React, Vue, Svelte, and vanilla JavaScript with Tree-shaking support.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
@@ -11,29 +11,73 @@
11
11
  "require": "./dist/index.cjs",
12
12
  "types": "./dist/index.d.ts"
13
13
  },
14
+ "./textToParticle": {
15
+ "import": "./dist/textToParticle/index.mjs",
16
+ "require": "./dist/textToParticle/index.cjs",
17
+ "types": "./dist/textToParticle/index.d.ts"
18
+ },
19
+ "./count": {
20
+ "import": "./dist/count/index.mjs",
21
+ "require": "./dist/count/index.cjs",
22
+ "types": "./dist/count/index.d.ts"
23
+ },
14
24
  "./react": {
15
25
  "import": "./dist/react/index.mjs",
16
26
  "require": "./dist/react/index.cjs",
17
27
  "types": "./dist/react/index.d.ts"
18
28
  },
29
+ "./react/textToParticle": {
30
+ "import": "./dist/react/textToParticle/index.mjs",
31
+ "require": "./dist/react/textToParticle/index.cjs",
32
+ "types": "./dist/react/textToParticle/index.d.ts"
33
+ },
34
+ "./react/count": {
35
+ "import": "./dist/react/count/index.mjs",
36
+ "require": "./dist/react/count/index.cjs",
37
+ "types": "./dist/react/count/index.d.ts"
38
+ },
19
39
  "./vue": {
20
40
  "import": "./dist/vue/index.mjs",
21
41
  "require": "./dist/vue/index.cjs",
22
42
  "types": "./dist/vue/index.d.ts"
23
43
  },
44
+ "./vue/textToParticle": {
45
+ "import": "./dist/vue/textToParticle/index.mjs",
46
+ "require": "./dist/vue/textToParticle/index.cjs",
47
+ "types": "./dist/vue/textToParticle/index.d.ts"
48
+ },
49
+ "./vue/count": {
50
+ "import": "./dist/vue/count/index.mjs",
51
+ "require": "./dist/vue/count/index.cjs",
52
+ "types": "./dist/vue/count/index.d.ts"
53
+ },
24
54
  "./svelte": {
25
55
  "import": "./dist/svelte/index.mjs",
26
56
  "require": "./dist/svelte/index.cjs",
27
57
  "types": "./dist/svelte/index.d.ts"
58
+ },
59
+ "./svelte/textToParticle": {
60
+ "import": "./dist/svelte/textToParticle/index.mjs",
61
+ "require": "./dist/svelte/textToParticle/index.cjs",
62
+ "types": "./dist/svelte/textToParticle/index.d.ts"
63
+ },
64
+ "./svelte/count": {
65
+ "import": "./dist/svelte/count/index.mjs",
66
+ "require": "./dist/svelte/count/index.cjs",
67
+ "types": "./dist/svelte/count/index.d.ts"
28
68
  }
29
69
  },
30
70
  "files": [
31
71
  "dist"
32
72
  ],
33
73
  "scripts": {
34
- "build": "rollup -c rollup.config.mjs && npm run build:vue && npm run build:svelte && node scripts/generate-react-types.js",
74
+ "build": "rollup -c rollup.config.mjs && npm run build:vue && npm run build:vue:textToParticle && npm run build:vue:count && npm run build:svelte && npm run build:svelte:textToParticle && npm run build:svelte:count && node scripts/generate-react-types.js",
35
75
  "build:vue": "vite build --config vite.config.vue.mjs",
76
+ "build:vue:textToParticle": "vite build --config vite.config.vue.textToParticle.mjs",
77
+ "build:vue:count": "vite build --config vite.config.vue.count.mjs",
36
78
  "build:svelte": "vite build --config vite.config.svelte.mjs",
79
+ "build:svelte:textToParticle": "vite build --config vite.config.svelte.textToParticle.mjs",
80
+ "build:svelte:count": "vite build --config vite.config.svelte.count.mjs",
37
81
  "dev": "rollup -c rollup.config.mjs -w",
38
82
  "serve": "npx http-server . -p 8080 -o --cors",
39
83
  "dev:example": "npm run serve",
@@ -41,13 +85,18 @@
41
85
  "prepublishOnly": "npm run build && npm run precheck"
42
86
  },
43
87
  "keywords": [
88
+ "animation",
89
+ "effects",
44
90
  "particles",
45
91
  "morphing",
92
+ "count",
46
93
  "canvas",
47
- "animation",
48
94
  "react",
49
95
  "vue",
50
- "vanilla-js"
96
+ "svelte",
97
+ "vanilla-js",
98
+ "tree-shaking",
99
+ "typescript"
51
100
  ],
52
101
  "author": "mason.dev fe.hyunsu@gmail.com",
53
102
  "license": "MIT",