masoneffect 0.1.10 → 0.1.12

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.
package/README.md CHANGED
@@ -1,16 +1,18 @@
1
1
  # MasonEffect
2
2
 
3
- 파티클 모핑 효과를 제공하는 라이브러리입니다. React, Vue, 그리고 바닐라 JavaScript에서 사용할 수 있습니다.
3
+ A library that provides particle morphing effects. It can be used with React, Vue, and vanilla JavaScript.
4
4
 
5
- ## 설치
5
+ ## Installation
6
6
 
7
7
  ```bash
8
8
  npm install masoneffect
9
9
  ```
10
10
 
11
- ## 사용법
11
+ ## Usage
12
12
 
13
- ### 바닐라 JavaScript
13
+ ### Vanilla JavaScript
14
+
15
+ #### Using npm (ES modules)
14
16
 
15
17
  ```javascript
16
18
  import { MasonEffect } from 'masoneffect';
@@ -22,20 +24,36 @@ const effect = new MasonEffect(container, {
22
24
  maxParticles: 2000,
23
25
  });
24
26
 
25
- // 텍스트 변경
27
+ // Change text
26
28
  effect.morph('New Text');
27
29
 
28
- // 텍스트와 함께 다른 속성도 변경
30
+ // Change text along with other properties
29
31
  effect.morph({
30
32
  text: 'New Text',
31
33
  particleColor: '#ff00ff',
32
34
  maxParticles: 3000,
33
35
  });
34
36
 
35
- // 파티클을 초기 위치로 돌아가기
37
+ // Return particles to initial position
36
38
  effect.scatter();
37
39
  ```
38
40
 
41
+ #### Using CDN (UMD)
42
+
43
+ ```html
44
+ <script src="https://unpkg.com/masoneffect/dist/index.umd.min.js"></script>
45
+ <script>
46
+ const container = document.getElementById('my-container');
47
+ const effect = new MasonEffect.MasonEffect(container, {
48
+ text: 'Hello World',
49
+ particleColor: '#00ff88',
50
+ maxParticles: 2000,
51
+ });
52
+
53
+ effect.morph('New Text');
54
+ </script>
55
+ ```
56
+
39
57
  ### React
40
58
 
41
59
  ```jsx
@@ -78,7 +96,7 @@ function App() {
78
96
  }
79
97
  ```
80
98
 
81
- **⚠️ 주의**: React 컴포넌트 사용 컨테이너에 명시적인 크기를 지정해야 합니다. 자세한 내용은 [React 문제 해결 가이드](./REACT_TROUBLESHOOTING.md)를 참고하세요.
99
+ **⚠️ Note**: When using the React component, you must specify an explicit size for the container. For more details, see the [React Troubleshooting Guide](./REACT_TROUBLESHOOTING.md).
82
100
 
83
101
  ### Vue 3
84
102
 
@@ -128,37 +146,38 @@ const onReady = (instance) => {
128
146
 
129
147
  ## API
130
148
 
131
- ### 옵션
132
-
133
- | 옵션 | 타입 | 기본값 | 설명 |
134
- |------|------|--------|------|
135
- | `text` | `string` | `'mason effect'` | 표시할 텍스트 |
136
- | `densityStep` | `number` | `2` | 파티클 샘플링 밀도 (작을수록 촘촘함) |
137
- | `maxParticles` | `number` | `3200` | 최대 파티클 |
138
- | `pointSize` | `number` | `0.5` | 파티클 크기 |
139
- | `ease` | `number` | `0.05` | 이동 가속도 |
140
- | `repelRadius` | `number` | `150` | 마우스 반발 범위 |
141
- | `repelStrength` | `number` | `1` | 마우스 반발 세기 |
142
- | `particleColor` | `string` | `'#fff'` | 파티클 색상 |
143
- | `fontFamily` | `string` | `'Inter, system-ui, Arial'` | 폰트 패밀리 |
144
- | `fontSize` | `number \| null` | `null` | 폰트 크기 (null이면 자동) |
145
- | `width` | `number \| null` | `null` | 캔버스 너비 (null이면 컨테이너 크기) |
146
- | `height` | `number \| null` | `null` | 캔버스 높이 (null이면 컨테이너 크기) |
147
- | `devicePixelRatio` | `number \| null` | `null` | 디바이스 픽셀 비율 (null이면 자동) |
148
- | `onReady` | `function` | `null` | 초기화 완료 콜백 |
149
- | `onUpdate` | `function` | `null` | 업데이트 콜백 |
150
-
151
- ### 메서드
149
+ ### Options
150
+
151
+ | Option | Type | Default | Description |
152
+ |--------|------|---------|-------------|
153
+ | `text` | `string` | `'mason effect'` | Text to display |
154
+ | `densityStep` | `number` | `2` | Particle sampling density (smaller = denser) |
155
+ | `maxParticles` | `number` | `3200` | Maximum number of particles |
156
+ | `pointSize` | `number` | `0.5` | Particle point size |
157
+ | `ease` | `number` | `0.05` | Movement acceleration |
158
+ | `repelRadius` | `number` | `150` | Mouse repel radius |
159
+ | `repelStrength` | `number` | `1` | Mouse repel strength |
160
+ | `particleColor` | `string` | `'#fff'` | Particle color |
161
+ | `fontFamily` | `string` | `'Inter, system-ui, Arial'` | Font family |
162
+ | `fontSize` | `number \| null` | `null` | Font size (auto if null) |
163
+ | `width` | `number \| null` | `null` | Canvas width (container size if null) |
164
+ | `height` | `number \| null` | `null` | Canvas height (container size if null) |
165
+ | `devicePixelRatio` | `number \| null` | `null` | Device pixel ratio (auto if null) |
166
+ | `debounceDelay` | `number` | `150` | Debounce delay in milliseconds for resize, morph, and updateConfig (set to 0 to disable) |
167
+ | `onReady` | `function` | `null` | Initialization complete callback |
168
+ | `onUpdate` | `function` | `null` | Update callback |
169
+
170
+ ### Methods
152
171
 
153
172
  #### `morph(textOrOptions?: string | Partial<MasonEffectOptions>)`
154
- 텍스트 형태로 파티클을 모핑합니다.
173
+ Morphs particles into text form. This method is debounced to prevent excessive calls (default: 150ms delay).
155
174
 
156
- **문자열 사용:**
175
+ **Using string:**
157
176
  ```javascript
158
177
  effect.morph('New Text');
159
178
  ```
160
179
 
161
- **객체 사용 (텍스트와 함께 다른 속성도 변경):**
180
+ **Using object (change text along with other properties):**
162
181
  ```javascript
163
182
  effect.morph({
164
183
  text: 'New Text',
@@ -169,52 +188,57 @@ effect.morph({
169
188
  });
170
189
  ```
171
190
 
191
+ **Note**: Multiple rapid calls will be debounced, and only the last call will be executed after the delay period.
192
+
172
193
  #### `scatter()`
173
- 파티클을 초기 위치로 돌아가게 합니다. 파티클이 처음 생성되었을 때의 위치로 복귀합니다.
194
+ Returns particles to their initial position. Each particle returns to the position where it was first created.
174
195
 
175
196
  #### `updateConfig(config: Partial<MasonEffectOptions>)`
176
- 설정을 업데이트합니다.
197
+ Updates the configuration. This method is debounced to prevent excessive calls (default: 150ms delay).
177
198
 
178
199
  #### `destroy()`
179
- 인스턴스를 파괴하고 리소스를 정리합니다.
200
+ Destroys the instance and cleans up resources.
180
201
 
181
- ## 특징
202
+ ## Features
182
203
 
183
- - 🎨 텍스트를 파티클로 변환하는 모핑 효과
184
- - 🖱️ 마우스 인터랙션 지원 (반발/흡입)
185
- - 📱 반응형 디자인
186
- - ⚡ 고성능 Canvas 렌더링
187
- - 🔧 React, Vue, 바닐라 JS 모두 지원
188
- - 🎯 TypeScript 타입 정의 포함
189
- - 💾 프로덕션 빌드 자동 난독화 최적화
190
- - 🔄 초기 위치로 복귀하는 scatter 효과
204
+ - 🎨 Morphing effect that converts text into particles
205
+ - 🖱️ Mouse interaction support (repel/attract)
206
+ - 📱 Responsive design
207
+ - ⚡ High-performance Canvas rendering
208
+ - 🔧 Supports React, Vue, and vanilla JS (including CDN)
209
+ - 🎯 Includes TypeScript type definitions
210
+ - 💾 Automatic obfuscation and optimization in production builds
211
+ - 🔄 Scatter effect that returns to initial position
212
+ - 👁️ **IntersectionObserver**: Automatically pauses animation when not visible (saves resources)
213
+ - ⏱️ **Debouncing**: Prevents excessive calls on resize, morph, and updateConfig methods
214
+ - 🎛️ **Configurable debounce delay**: Adjust or disable debouncing via `debounceDelay` option
191
215
 
192
- ## 개발
216
+ ## Development
193
217
 
194
218
  ```bash
195
- # 의존성 설치
219
+ # Install dependencies
196
220
  npm install
197
221
 
198
- # 개발 모드 (watch)
222
+ # Development mode (watch)
199
223
  npm run dev
200
224
 
201
- # 빌드 (프로덕션용 min 파일 생성)
225
+ # Build (generate production min files)
202
226
  npm run build
203
227
 
204
- # 예제 테스트 서버
228
+ # Example test server
205
229
  npm run serve
206
230
  ```
207
231
 
208
- ## 빌드 결과물
232
+ ## Build Output
209
233
 
210
- 빌드를 실행하면 다음 파일들이 생성됩니다:
234
+ Running the build will generate the following files:
211
235
 
212
- - **개발용**: `dist/index.js`, `dist/index.esm.js` (소스맵 포함)
213
- - **프로덕션용**: `dist/index.min.js`, `dist/index.esm.min.js` (난독화 최적화)
214
- - **React 컴포넌트**: `dist/react/MasonEffect.min.js` (난독화)
236
+ - **Development**: `dist/index.js`, `dist/index.esm.js`, `dist/index.umd.js` (with source maps)
237
+ - **Production**: `dist/index.min.js`, `dist/index.esm.min.js`, `dist/index.umd.min.js` (obfuscated and optimized)
238
+ - **React component**: `dist/react/MasonEffect.min.js` (obfuscated)
215
239
 
216
- npm으로 설치하면 자동으로 min 파일이 사용됩니다. 자세한 내용은 [빌드 가이드](./BUILD.md)를 참고하세요.
240
+ When installed via npm, min files are automatically used. The UMD files (`index.umd.min.js`) can be used directly in browsers via CDN or script tags. For more details, see the [Build Guide](./BUILD.md).
217
241
 
218
- ## 라이선스
242
+ ## License
219
243
 
220
244
  MIT
package/dist/index.esm.js CHANGED
@@ -3,6 +3,19 @@
3
3
  * 바닐라 JS 코어 클래스
4
4
  */
5
5
 
6
+ // 디바운스 유틸리티 함수
7
+ function debounce(func, wait) {
8
+ let timeout;
9
+ return function executedFunction(...args) {
10
+ const later = () => {
11
+ clearTimeout(timeout);
12
+ func.apply(this, args);
13
+ };
14
+ clearTimeout(timeout);
15
+ timeout = setTimeout(later, wait);
16
+ };
17
+ }
18
+
6
19
  class MasonEffect {
7
20
  constructor(container, options = {}) {
8
21
  // 컨테이너 요소
@@ -51,14 +64,24 @@ class MasonEffect {
51
64
  this.mouse = { x: 0, y: 0, down: false };
52
65
  this.animationId = null;
53
66
  this.isRunning = false;
67
+ this.isVisible = false;
68
+ this.intersectionObserver = null;
54
69
 
55
- // 이벤트 핸들러 바인딩
56
- this.handleResize = this.handleResize.bind(this);
70
+ // 디바운스 설정 (ms)
71
+ this.debounceDelay = options.debounceDelay ?? 150; // 기본 150ms
72
+
73
+ // 이벤트 핸들러 바인딩 (디바운스 적용 전에 바인딩)
74
+ const boundHandleResize = this.handleResize.bind(this);
75
+ this.handleResize = debounce(boundHandleResize, this.debounceDelay);
57
76
  this.handleMouseMove = this.handleMouseMove.bind(this);
58
77
  this.handleMouseLeave = this.handleMouseLeave.bind(this);
59
78
  this.handleMouseDown = this.handleMouseDown.bind(this);
60
79
  this.handleMouseUp = this.handleMouseUp.bind(this);
61
80
 
81
+ // morph와 updateConfig를 위한 디바운스된 내부 메서드
82
+ this._debouncedMorph = debounce(this._morphInternal.bind(this), this.debounceDelay);
83
+ this._debouncedUpdateConfig = debounce(this._updateConfigInternal.bind(this), this.debounceDelay);
84
+
62
85
  // 초기화
63
86
  this.init();
64
87
  }
@@ -66,13 +89,48 @@ class MasonEffect {
66
89
  init() {
67
90
  this.resize();
68
91
  this.setupEventListeners();
69
- this.start();
70
-
92
+ this.setupIntersectionObserver();
93
+
71
94
  if (this.config.onReady) {
72
95
  this.config.onReady(this);
73
96
  }
74
97
  }
75
98
 
99
+ setupIntersectionObserver() {
100
+ // IntersectionObserver가 지원되지 않는 환경에서는 항상 재생
101
+ if (typeof window === 'undefined' || typeof window.IntersectionObserver === 'undefined') {
102
+ this.isVisible = true;
103
+ this.start();
104
+ return;
105
+ }
106
+
107
+ // 이미 설정되어 있다면 다시 만들지 않음
108
+ if (this.intersectionObserver) {
109
+ return;
110
+ }
111
+
112
+ this.intersectionObserver = new IntersectionObserver(
113
+ (entries) => {
114
+ for (const entry of entries) {
115
+ if (entry.target !== this.container) continue;
116
+
117
+ if (entry.isIntersecting) {
118
+ this.isVisible = true;
119
+ this.start();
120
+ } else {
121
+ this.isVisible = false;
122
+ this.stop();
123
+ }
124
+ }
125
+ },
126
+ {
127
+ threshold: 0.1, // 10% 이상 보일 때 동작
128
+ }
129
+ );
130
+
131
+ this.intersectionObserver.observe(this.container);
132
+ }
133
+
76
134
  resize() {
77
135
  const width = this.config.width || this.container.clientWidth || window.innerWidth;
78
136
  const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;
@@ -202,6 +260,12 @@ class MasonEffect {
202
260
  }
203
261
 
204
262
  morph(textOrOptions = null) {
263
+ // 즉시 실행이 필요한 경우 (예: 초기화 시)를 위해 내부 메서드 직접 호출
264
+ // 일반적인 경우에는 디바운스 적용
265
+ this._debouncedMorph(textOrOptions);
266
+ }
267
+
268
+ _morphInternal(textOrOptions = null) {
205
269
  // W와 H가 0이면 resize 먼저 실행
206
270
  if (this.W === 0 || this.H === 0) {
207
271
  this.resize();
@@ -332,6 +396,11 @@ class MasonEffect {
332
396
 
333
397
  // 설정 업데이트
334
398
  updateConfig(newConfig) {
399
+ // 디바운스 적용
400
+ this._debouncedUpdateConfig(newConfig);
401
+ }
402
+
403
+ _updateConfigInternal(newConfig) {
335
404
  this.config = { ...this.config, ...newConfig };
336
405
  if (newConfig.text) {
337
406
  this.buildTargets();
@@ -342,6 +411,10 @@ class MasonEffect {
342
411
  destroy() {
343
412
  this.stop();
344
413
  this.removeEventListeners();
414
+ if (this.intersectionObserver) {
415
+ this.intersectionObserver.disconnect();
416
+ this.intersectionObserver = null;
417
+ }
345
418
  if (this.canvas && this.canvas.parentNode) {
346
419
  this.canvas.parentNode.removeChild(this.canvas);
347
420
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/core/index.js"],"sourcesContent":["/**\n * MasonEffect - 파티클 모핑 효과 라이브러리\n * 바닐라 JS 코어 클래스\n */\n\nexport class MasonEffect {\n constructor(container, options = {}) {\n // 컨테이너 요소\n this.container = typeof container === 'string' \n ? document.querySelector(container) \n : container;\n \n if (!this.container) {\n throw new Error('Container element not found');\n }\n\n // 설정값들\n this.config = {\n text: options.text || 'mason effect',\n densityStep: options.densityStep ?? 2,\n maxParticles: options.maxParticles ?? 3200,\n pointSize: options.pointSize ?? 0.5,\n ease: options.ease ?? 0.05,\n repelRadius: options.repelRadius ?? 150,\n repelStrength: options.repelStrength ?? 1,\n particleColor: options.particleColor || '#fff',\n fontFamily: options.fontFamily || 'Inter, system-ui, Arial',\n fontSize: options.fontSize || null, // null이면 자동 계산\n width: options.width || null, // null이면 컨테이너 크기\n height: options.height || null, // null이면 컨테이너 크기\n devicePixelRatio: options.devicePixelRatio ?? null, // null이면 자동\n onReady: options.onReady || null,\n onUpdate: options.onUpdate || null,\n };\n\n // 캔버스 생성\n this.canvas = document.createElement('canvas');\n this.ctx = this.canvas.getContext('2d');\n this.container.appendChild(this.canvas);\n this.canvas.style.display = 'block';\n\n // 오프스크린 캔버스 (텍스트 렌더링용)\n this.offCanvas = document.createElement('canvas');\n this.offCtx = this.offCanvas.getContext('2d');\n\n // 상태\n this.W = 0;\n this.H = 0;\n this.DPR = this.config.devicePixelRatio || Math.min(window.devicePixelRatio || 1, 1.8);\n this.particles = [];\n this.mouse = { x: 0, y: 0, down: false };\n this.animationId = null;\n this.isRunning = false;\n\n // 이벤트 핸들러 바인딩\n this.handleResize = this.handleResize.bind(this);\n this.handleMouseMove = this.handleMouseMove.bind(this);\n this.handleMouseLeave = this.handleMouseLeave.bind(this);\n this.handleMouseDown = this.handleMouseDown.bind(this);\n this.handleMouseUp = this.handleMouseUp.bind(this);\n\n // 초기화\n this.init();\n }\n\n init() {\n this.resize();\n this.setupEventListeners();\n this.start();\n \n if (this.config.onReady) {\n this.config.onReady(this);\n }\n }\n\n resize() {\n const width = this.config.width || this.container.clientWidth || window.innerWidth;\n const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;\n \n this.W = Math.floor(width * this.DPR);\n this.H = Math.floor(height * this.DPR);\n \n this.canvas.width = this.W;\n this.canvas.height = this.H;\n this.canvas.style.width = width + 'px';\n this.canvas.style.height = height + 'px';\n\n this.buildTargets();\n if (!this.particles.length) {\n this.initParticles();\n }\n }\n\n buildTargets() {\n const text = this.config.text;\n this.offCanvas.width = this.W;\n this.offCanvas.height = this.H;\n this.offCtx.clearRect(0, 0, this.offCanvas.width, this.offCanvas.height);\n\n const base = Math.min(this.W, this.H);\n const fontSize = this.config.fontSize || Math.max(80, Math.floor(base * 0.18));\n this.offCtx.fillStyle = '#ffffff';\n this.offCtx.textAlign = 'center';\n this.offCtx.textBaseline = 'middle';\n this.offCtx.font = `400 ${fontSize}px ${this.config.fontFamily}`;\n\n // 글자 간격 계산 및 그리기\n const chars = text.split('');\n const spacing = fontSize * 0.05;\n const totalWidth = this.offCtx.measureText(text).width + spacing * (chars.length - 1);\n let x = this.W / 2 - totalWidth / 2;\n \n for (const ch of chars) {\n this.offCtx.fillText(ch, x + this.offCtx.measureText(ch).width / 2, this.H / 2);\n x += this.offCtx.measureText(ch).width + spacing;\n }\n\n // 픽셀 샘플링\n const step = Math.max(2, this.config.densityStep);\n const img = this.offCtx.getImageData(0, 0, this.W, this.H).data;\n const targets = [];\n \n for (let y = 0; y < this.H; y += step) {\n for (let x = 0; x < this.W; x += step) {\n const i = (y * this.W + x) * 4;\n if (img[i] + img[i + 1] + img[i + 2] > 600) {\n targets.push({ x, y });\n }\n }\n }\n\n // 파티클 수 제한\n while (targets.length > this.config.maxParticles) {\n targets.splice(Math.floor(Math.random() * targets.length), 1);\n }\n\n // 파티클 수 조정\n if (this.particles.length < targets.length) {\n const need = targets.length - this.particles.length;\n for (let i = 0; i < need; i++) {\n this.particles.push(this.makeParticle());\n }\n } else if (this.particles.length > targets.length) {\n this.particles.length = targets.length;\n }\n\n // 목표 좌표 할당\n for (let i = 0; i < this.particles.length; i++) {\n const p = this.particles[i];\n const t = targets[i];\n p.tx = t.x;\n p.ty = t.y;\n }\n }\n\n makeParticle() {\n // 캔버스 전체에 골고루 분포 (여백 없이)\n const sx = Math.random() * this.W;\n const sy = Math.random() * this.H;\n return {\n x: sx,\n y: sy,\n vx: 0,\n vy: 0,\n tx: sx,\n ty: sy,\n initialX: sx, // 초기 위치 저장 (scatter 시 돌아갈 위치)\n initialY: sy,\n j: Math.random() * Math.PI * 2,\n };\n }\n\n initParticles() {\n // 캔버스 전체에 골고루 분포 (여백 없이)\n for (const p of this.particles) {\n const sx = Math.random() * this.W;\n const sy = Math.random() * this.H;\n p.x = sx;\n p.y = sy;\n p.vx = p.vy = 0;\n // 초기 위치 저장 (scatter 시 돌아갈 위치)\n p.initialX = sx;\n p.initialY = sy;\n }\n }\n\n scatter() {\n // 각 파티클을 초기 위치로 돌아가도록 설정\n for (const p of this.particles) {\n // 초기 위치가 저장되어 있으면 그 위치로, 없으면 현재 위치 유지\n if (p.initialX !== undefined && p.initialY !== undefined) {\n p.tx = p.initialX;\n p.ty = p.initialY;\n } else {\n // 초기 위치가 없으면 현재 위치를 초기 위치로 저장\n p.initialX = p.x;\n p.initialY = p.y;\n p.tx = p.initialX;\n p.ty = p.initialY;\n }\n }\n }\n\n morph(textOrOptions = null) {\n // W와 H가 0이면 resize 먼저 실행\n if (this.W === 0 || this.H === 0) {\n this.resize();\n }\n \n if (typeof textOrOptions === 'string') {\n // 문자열인 경우: 기존 동작 유지\n this.config.text = textOrOptions;\n this.buildTargets();\n } else if (textOrOptions && typeof textOrOptions === 'object') {\n // 객체인 경우: 텍스트와 함께 다른 설정도 변경\n const needsRebuild = textOrOptions.text !== undefined;\n this.config = { ...this.config, ...textOrOptions };\n if (needsRebuild) {\n this.buildTargets();\n }\n } else {\n // null이거나 undefined인 경우: 현재 텍스트로 재빌드\n this.buildTargets();\n }\n }\n\n update() {\n this.ctx.clearRect(0, 0, this.W, this.H);\n\n for (const p of this.particles) {\n // 목표 좌표로 당기는 힘\n let ax = (p.tx - p.x) * this.config.ease;\n let ay = (p.ty - p.y) * this.config.ease;\n\n // 마우스 반발/흡입\n if (this.mouse.x || this.mouse.y) {\n const dx = p.x - this.mouse.x;\n const dy = p.y - this.mouse.y;\n const d2 = dx * dx + dy * dy;\n const r = this.config.repelRadius * this.DPR;\n if (d2 < r * r) {\n const d = Math.sqrt(d2) + 0.0001;\n const f = (this.mouse.down ? -1 : 1) * this.config.repelStrength * (1 - d / r);\n ax += (dx / d) * f * 6.0;\n ay += (dy / d) * f * 6.0;\n }\n }\n\n // 진동 효과\n p.j += 2;\n ax += Math.cos(p.j) * 0.05;\n ay += Math.sin(p.j * 1.3) * 0.05;\n\n // 속도와 위치 업데이트\n p.vx = (p.vx + ax) * Math.random();\n p.vy = (p.vy + ay) * Math.random();\n p.x += p.vx;\n p.y += p.vy;\n }\n\n // 파티클 그리기\n this.ctx.fillStyle = this.config.particleColor;\n const r = this.config.pointSize * this.DPR;\n for (const p of this.particles) {\n this.ctx.beginPath();\n this.ctx.arc(p.x, p.y, r, 0, Math.PI * 2);\n this.ctx.fill();\n }\n\n if (this.config.onUpdate) {\n this.config.onUpdate(this);\n }\n }\n\n animate() {\n if (!this.isRunning) return;\n this.update();\n this.animationId = requestAnimationFrame(() => this.animate());\n }\n\n start() {\n if (this.isRunning) return;\n this.isRunning = true;\n this.animate();\n }\n\n stop() {\n this.isRunning = false;\n if (this.animationId) {\n cancelAnimationFrame(this.animationId);\n this.animationId = null;\n }\n }\n\n setupEventListeners() {\n window.addEventListener('resize', this.handleResize);\n this.canvas.addEventListener('mousemove', this.handleMouseMove);\n this.canvas.addEventListener('mouseleave', this.handleMouseLeave);\n this.canvas.addEventListener('mousedown', this.handleMouseDown);\n window.addEventListener('mouseup', this.handleMouseUp);\n }\n\n removeEventListeners() {\n window.removeEventListener('resize', this.handleResize);\n this.canvas.removeEventListener('mousemove', this.handleMouseMove);\n this.canvas.removeEventListener('mouseleave', this.handleMouseLeave);\n this.canvas.removeEventListener('mousedown', this.handleMouseDown);\n window.removeEventListener('mouseup', this.handleMouseUp);\n }\n\n handleResize() {\n this.resize();\n }\n\n handleMouseMove(e) {\n const rect = this.canvas.getBoundingClientRect();\n this.mouse.x = (e.clientX - rect.left) * this.DPR;\n this.mouse.y = (e.clientY - rect.top) * this.DPR;\n }\n\n handleMouseLeave() {\n this.mouse.x = this.mouse.y = 0;\n }\n\n handleMouseDown() {\n this.mouse.down = true;\n }\n\n handleMouseUp() {\n this.mouse.down = false;\n }\n\n // 설정 업데이트\n updateConfig(newConfig) {\n this.config = { ...this.config, ...newConfig };\n if (newConfig.text) {\n this.buildTargets();\n }\n }\n\n // 파괴 및 정리\n destroy() {\n this.stop();\n this.removeEventListeners();\n if (this.canvas && this.canvas.parentNode) {\n this.canvas.parentNode.removeChild(this.canvas);\n }\n }\n}\n\n// 기본 export\nexport default MasonEffect;\n\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/core/index.js"],"sourcesContent":["/**\n * MasonEffect - 파티클 모핑 효과 라이브러리\n * 바닐라 JS 코어 클래스\n */\n\n// 디바운스 유틸리티 함수\nfunction debounce(func, wait) {\n let timeout;\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func.apply(this, args);\n };\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n}\n\nexport class MasonEffect {\n constructor(container, options = {}) {\n // 컨테이너 요소\n this.container = typeof container === 'string' \n ? document.querySelector(container) \n : container;\n \n if (!this.container) {\n throw new Error('Container element not found');\n }\n\n // 설정값들\n this.config = {\n text: options.text || 'mason effect',\n densityStep: options.densityStep ?? 2,\n maxParticles: options.maxParticles ?? 3200,\n pointSize: options.pointSize ?? 0.5,\n ease: options.ease ?? 0.05,\n repelRadius: options.repelRadius ?? 150,\n repelStrength: options.repelStrength ?? 1,\n particleColor: options.particleColor || '#fff',\n fontFamily: options.fontFamily || 'Inter, system-ui, Arial',\n fontSize: options.fontSize || null, // null이면 자동 계산\n width: options.width || null, // null이면 컨테이너 크기\n height: options.height || null, // null이면 컨테이너 크기\n devicePixelRatio: options.devicePixelRatio ?? null, // null이면 자동\n onReady: options.onReady || null,\n onUpdate: options.onUpdate || null,\n };\n\n // 캔버스 생성\n this.canvas = document.createElement('canvas');\n this.ctx = this.canvas.getContext('2d');\n this.container.appendChild(this.canvas);\n this.canvas.style.display = 'block';\n\n // 오프스크린 캔버스 (텍스트 렌더링용)\n this.offCanvas = document.createElement('canvas');\n this.offCtx = this.offCanvas.getContext('2d');\n\n // 상태\n this.W = 0;\n this.H = 0;\n this.DPR = this.config.devicePixelRatio || Math.min(window.devicePixelRatio || 1, 1.8);\n this.particles = [];\n this.mouse = { x: 0, y: 0, down: false };\n this.animationId = null;\n this.isRunning = false;\n this.isVisible = false;\n this.intersectionObserver = null;\n\n // 디바운스 설정 (ms)\n this.debounceDelay = options.debounceDelay ?? 150; // 기본 150ms\n\n // 이벤트 핸들러 바인딩 (디바운스 적용 전에 바인딩)\n const boundHandleResize = this.handleResize.bind(this);\n this.handleResize = debounce(boundHandleResize, this.debounceDelay);\n this.handleMouseMove = this.handleMouseMove.bind(this);\n this.handleMouseLeave = this.handleMouseLeave.bind(this);\n this.handleMouseDown = this.handleMouseDown.bind(this);\n this.handleMouseUp = this.handleMouseUp.bind(this);\n\n // morph와 updateConfig를 위한 디바운스된 내부 메서드\n this._debouncedMorph = debounce(this._morphInternal.bind(this), this.debounceDelay);\n this._debouncedUpdateConfig = debounce(this._updateConfigInternal.bind(this), this.debounceDelay);\n\n // 초기화\n this.init();\n }\n\n init() {\n this.resize();\n this.setupEventListeners();\n this.setupIntersectionObserver();\n\n if (this.config.onReady) {\n this.config.onReady(this);\n }\n }\n\n setupIntersectionObserver() {\n // IntersectionObserver가 지원되지 않는 환경에서는 항상 재생\n if (typeof window === 'undefined' || typeof window.IntersectionObserver === 'undefined') {\n this.isVisible = true;\n this.start();\n return;\n }\n\n // 이미 설정되어 있다면 다시 만들지 않음\n if (this.intersectionObserver) {\n return;\n }\n\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.target !== this.container) continue;\n\n if (entry.isIntersecting) {\n this.isVisible = true;\n this.start();\n } else {\n this.isVisible = false;\n this.stop();\n }\n }\n },\n {\n threshold: 0.1, // 10% 이상 보일 때 동작\n }\n );\n\n this.intersectionObserver.observe(this.container);\n }\n\n resize() {\n const width = this.config.width || this.container.clientWidth || window.innerWidth;\n const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;\n \n this.W = Math.floor(width * this.DPR);\n this.H = Math.floor(height * this.DPR);\n \n this.canvas.width = this.W;\n this.canvas.height = this.H;\n this.canvas.style.width = width + 'px';\n this.canvas.style.height = height + 'px';\n\n this.buildTargets();\n if (!this.particles.length) {\n this.initParticles();\n }\n }\n\n buildTargets() {\n const text = this.config.text;\n this.offCanvas.width = this.W;\n this.offCanvas.height = this.H;\n this.offCtx.clearRect(0, 0, this.offCanvas.width, this.offCanvas.height);\n\n const base = Math.min(this.W, this.H);\n const fontSize = this.config.fontSize || Math.max(80, Math.floor(base * 0.18));\n this.offCtx.fillStyle = '#ffffff';\n this.offCtx.textAlign = 'center';\n this.offCtx.textBaseline = 'middle';\n this.offCtx.font = `400 ${fontSize}px ${this.config.fontFamily}`;\n\n // 글자 간격 계산 및 그리기\n const chars = text.split('');\n const spacing = fontSize * 0.05;\n const totalWidth = this.offCtx.measureText(text).width + spacing * (chars.length - 1);\n let x = this.W / 2 - totalWidth / 2;\n \n for (const ch of chars) {\n this.offCtx.fillText(ch, x + this.offCtx.measureText(ch).width / 2, this.H / 2);\n x += this.offCtx.measureText(ch).width + spacing;\n }\n\n // 픽셀 샘플링\n const step = Math.max(2, this.config.densityStep);\n const img = this.offCtx.getImageData(0, 0, this.W, this.H).data;\n const targets = [];\n \n for (let y = 0; y < this.H; y += step) {\n for (let x = 0; x < this.W; x += step) {\n const i = (y * this.W + x) * 4;\n if (img[i] + img[i + 1] + img[i + 2] > 600) {\n targets.push({ x, y });\n }\n }\n }\n\n // 파티클 수 제한\n while (targets.length > this.config.maxParticles) {\n targets.splice(Math.floor(Math.random() * targets.length), 1);\n }\n\n // 파티클 수 조정\n if (this.particles.length < targets.length) {\n const need = targets.length - this.particles.length;\n for (let i = 0; i < need; i++) {\n this.particles.push(this.makeParticle());\n }\n } else if (this.particles.length > targets.length) {\n this.particles.length = targets.length;\n }\n\n // 목표 좌표 할당\n for (let i = 0; i < this.particles.length; i++) {\n const p = this.particles[i];\n const t = targets[i];\n p.tx = t.x;\n p.ty = t.y;\n }\n }\n\n makeParticle() {\n // 캔버스 전체에 골고루 분포 (여백 없이)\n const sx = Math.random() * this.W;\n const sy = Math.random() * this.H;\n return {\n x: sx,\n y: sy,\n vx: 0,\n vy: 0,\n tx: sx,\n ty: sy,\n initialX: sx, // 초기 위치 저장 (scatter 시 돌아갈 위치)\n initialY: sy,\n j: Math.random() * Math.PI * 2,\n };\n }\n\n initParticles() {\n // 캔버스 전체에 골고루 분포 (여백 없이)\n for (const p of this.particles) {\n const sx = Math.random() * this.W;\n const sy = Math.random() * this.H;\n p.x = sx;\n p.y = sy;\n p.vx = p.vy = 0;\n // 초기 위치 저장 (scatter 시 돌아갈 위치)\n p.initialX = sx;\n p.initialY = sy;\n }\n }\n\n scatter() {\n // 각 파티클을 초기 위치로 돌아가도록 설정\n for (const p of this.particles) {\n // 초기 위치가 저장되어 있으면 그 위치로, 없으면 현재 위치 유지\n if (p.initialX !== undefined && p.initialY !== undefined) {\n p.tx = p.initialX;\n p.ty = p.initialY;\n } else {\n // 초기 위치가 없으면 현재 위치를 초기 위치로 저장\n p.initialX = p.x;\n p.initialY = p.y;\n p.tx = p.initialX;\n p.ty = p.initialY;\n }\n }\n }\n\n morph(textOrOptions = null) {\n // 즉시 실행이 필요한 경우 (예: 초기화 시)를 위해 내부 메서드 직접 호출\n // 일반적인 경우에는 디바운스 적용\n this._debouncedMorph(textOrOptions);\n }\n\n _morphInternal(textOrOptions = null) {\n // W와 H가 0이면 resize 먼저 실행\n if (this.W === 0 || this.H === 0) {\n this.resize();\n }\n \n if (typeof textOrOptions === 'string') {\n // 문자열인 경우: 기존 동작 유지\n this.config.text = textOrOptions;\n this.buildTargets();\n } else if (textOrOptions && typeof textOrOptions === 'object') {\n // 객체인 경우: 텍스트와 함께 다른 설정도 변경\n const needsRebuild = textOrOptions.text !== undefined;\n this.config = { ...this.config, ...textOrOptions };\n if (needsRebuild) {\n this.buildTargets();\n }\n } else {\n // null이거나 undefined인 경우: 현재 텍스트로 재빌드\n this.buildTargets();\n }\n }\n\n update() {\n this.ctx.clearRect(0, 0, this.W, this.H);\n\n for (const p of this.particles) {\n // 목표 좌표로 당기는 힘\n let ax = (p.tx - p.x) * this.config.ease;\n let ay = (p.ty - p.y) * this.config.ease;\n\n // 마우스 반발/흡입\n if (this.mouse.x || this.mouse.y) {\n const dx = p.x - this.mouse.x;\n const dy = p.y - this.mouse.y;\n const d2 = dx * dx + dy * dy;\n const r = this.config.repelRadius * this.DPR;\n if (d2 < r * r) {\n const d = Math.sqrt(d2) + 0.0001;\n const f = (this.mouse.down ? -1 : 1) * this.config.repelStrength * (1 - d / r);\n ax += (dx / d) * f * 6.0;\n ay += (dy / d) * f * 6.0;\n }\n }\n\n // 진동 효과\n p.j += 2;\n ax += Math.cos(p.j) * 0.05;\n ay += Math.sin(p.j * 1.3) * 0.05;\n\n // 속도와 위치 업데이트\n p.vx = (p.vx + ax) * Math.random();\n p.vy = (p.vy + ay) * Math.random();\n p.x += p.vx;\n p.y += p.vy;\n }\n\n // 파티클 그리기\n this.ctx.fillStyle = this.config.particleColor;\n const r = this.config.pointSize * this.DPR;\n for (const p of this.particles) {\n this.ctx.beginPath();\n this.ctx.arc(p.x, p.y, r, 0, Math.PI * 2);\n this.ctx.fill();\n }\n\n if (this.config.onUpdate) {\n this.config.onUpdate(this);\n }\n }\n\n animate() {\n if (!this.isRunning) return;\n this.update();\n this.animationId = requestAnimationFrame(() => this.animate());\n }\n\n start() {\n if (this.isRunning) return;\n this.isRunning = true;\n this.animate();\n }\n\n stop() {\n this.isRunning = false;\n if (this.animationId) {\n cancelAnimationFrame(this.animationId);\n this.animationId = null;\n }\n }\n\n setupEventListeners() {\n window.addEventListener('resize', this.handleResize);\n this.canvas.addEventListener('mousemove', this.handleMouseMove);\n this.canvas.addEventListener('mouseleave', this.handleMouseLeave);\n this.canvas.addEventListener('mousedown', this.handleMouseDown);\n window.addEventListener('mouseup', this.handleMouseUp);\n }\n\n removeEventListeners() {\n window.removeEventListener('resize', this.handleResize);\n this.canvas.removeEventListener('mousemove', this.handleMouseMove);\n this.canvas.removeEventListener('mouseleave', this.handleMouseLeave);\n this.canvas.removeEventListener('mousedown', this.handleMouseDown);\n window.removeEventListener('mouseup', this.handleMouseUp);\n }\n\n handleResize() {\n this.resize();\n }\n\n handleMouseMove(e) {\n const rect = this.canvas.getBoundingClientRect();\n this.mouse.x = (e.clientX - rect.left) * this.DPR;\n this.mouse.y = (e.clientY - rect.top) * this.DPR;\n }\n\n handleMouseLeave() {\n this.mouse.x = this.mouse.y = 0;\n }\n\n handleMouseDown() {\n this.mouse.down = true;\n }\n\n handleMouseUp() {\n this.mouse.down = false;\n }\n\n // 설정 업데이트\n updateConfig(newConfig) {\n // 디바운스 적용\n this._debouncedUpdateConfig(newConfig);\n }\n\n _updateConfigInternal(newConfig) {\n this.config = { ...this.config, ...newConfig };\n if (newConfig.text) {\n this.buildTargets();\n }\n }\n\n // 파괴 및 정리\n destroy() {\n this.stop();\n this.removeEventListeners();\n if (this.intersectionObserver) {\n this.intersectionObserver.disconnect();\n this.intersectionObserver = null;\n }\n if (this.canvas && this.canvas.parentNode) {\n this.canvas.parentNode.removeChild(this.canvas);\n }\n }\n}\n\n// 기본 export\nexport default MasonEffect;\n\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- class t{constructor(t,s={}){if(this.container="string"==typeof t?document.querySelector(t):t,!this.container)throw Error("Container element not found");this.config={text:s.text||"mason effect",densityStep:s.densityStep??2,maxParticles:s.maxParticles??3200,pointSize:s.pointSize??.5,ease:s.ease??.05,repelRadius:s.repelRadius??150,repelStrength:s.repelStrength??1,particleColor:s.particleColor||"#fff",fontFamily:s.fontFamily||"Inter, system-ui, Arial",fontSize:s.fontSize||null,width:s.width||null,height:s.height||null,devicePixelRatio:s.devicePixelRatio??null,onReady:s.onReady||null,onUpdate:s.onUpdate||null},this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.container.appendChild(this.canvas),this.canvas.style.display="block",this.offCanvas=document.createElement("canvas"),this.offCtx=this.offCanvas.getContext("2d"),this.W=0,this.H=0,this.DPR=this.config.devicePixelRatio||Math.min(window.devicePixelRatio||1,1.8),this.particles=[],this.mouse={x:0,y:0,down:!1},this.animationId=null,this.isRunning=!1,this.handleResize=this.handleResize.bind(this),this.handleMouseMove=this.handleMouseMove.bind(this),this.handleMouseLeave=this.handleMouseLeave.bind(this),this.handleMouseDown=this.handleMouseDown.bind(this),this.handleMouseUp=this.handleMouseUp.bind(this),this.init()}init(){this.resize(),this.setupEventListeners(),this.start(),this.config.onReady&&this.config.onReady(this)}resize(){const t=this.config.width||this.container.clientWidth||window.innerWidth,s=this.config.height||this.container.clientHeight||.7*window.innerHeight;this.W=Math.floor(t*this.DPR),this.H=Math.floor(s*this.DPR),this.canvas.width=this.W,this.canvas.height=this.H,this.canvas.style.width=t+"px",this.canvas.style.height=s+"px",this.buildTargets(),this.particles.length||this.initParticles()}buildTargets(){const t=this.config.text;this.offCanvas.width=this.W,this.offCanvas.height=this.H,this.offCtx.clearRect(0,0,this.offCanvas.width,this.offCanvas.height);const s=Math.min(this.W,this.H),i=this.config.fontSize||Math.max(80,Math.floor(.18*s));this.offCtx.fillStyle="#ffffff",this.offCtx.textAlign="center",this.offCtx.textBaseline="middle",this.offCtx.font=`400 ${i}px ${this.config.fontFamily}`;const h=t.split(""),e=.05*i,o=this.offCtx.measureText(t).width+e*(h.length-1);let n=this.W/2-o/2;for(const t of h)this.offCtx.fillText(t,n+this.offCtx.measureText(t).width/2,this.H/2),n+=this.offCtx.measureText(t).width+e;const a=Math.max(2,this.config.densityStep),l=this.offCtx.getImageData(0,0,this.W,this.H).data,r=[];for(let t=0;t<this.H;t+=a)for(let s=0;s<this.W;s+=a){const i=4*(t*this.W+s);l[i]+l[i+1]+l[i+2]>600&&r.push({x:s,y:t})}for(;r.length>this.config.maxParticles;)r.splice(Math.floor(Math.random()*r.length),1);if(this.particles.length<r.length){const t=r.length-this.particles.length;for(let s=0;t>s;s++)this.particles.push(this.makeParticle())}else this.particles.length>r.length&&(this.particles.length=r.length);for(let t=0;t<this.particles.length;t++){const s=this.particles[t],i=r[t];s.tx=i.x,s.ty=i.y}}makeParticle(){const t=Math.random()*this.W,s=Math.random()*this.H;return{x:t,y:s,vx:0,vy:0,tx:t,ty:s,initialX:t,initialY:s,j:Math.random()*Math.PI*2}}initParticles(){for(const t of this.particles){const s=Math.random()*this.W,i=Math.random()*this.H;t.x=s,t.y=i,t.vx=t.vy=0,t.initialX=s,t.initialY=i}}scatter(){for(const t of this.particles)void 0!==t.initialX&&void 0!==t.initialY?(t.tx=t.initialX,t.ty=t.initialY):(t.initialX=t.x,t.initialY=t.y,t.tx=t.initialX,t.ty=t.initialY)}morph(t=null){if(0!==this.W&&0!==this.H||this.resize(),"string"==typeof t)this.config.text=t,this.buildTargets();else if(t&&"object"==typeof t){const s=void 0!==t.text;this.config={...this.config,...t},s&&this.buildTargets()}else this.buildTargets()}update(){this.ctx.clearRect(0,0,this.W,this.H);for(const t of this.particles){let s=(t.tx-t.x)*this.config.ease,i=(t.ty-t.y)*this.config.ease;if(this.mouse.x||this.mouse.y){const h=t.x-this.mouse.x,e=t.y-this.mouse.y,o=h*h+e*e,n=this.config.repelRadius*this.DPR;if(n*n>o){const t=Math.sqrt(o)+1e-4,a=(this.mouse.down?-1:1)*this.config.repelStrength*(1-t/n);s+=h/t*a*6,i+=e/t*a*6}}t.j+=2,s+=.05*Math.cos(t.j),i+=.05*Math.sin(1.3*t.j),t.vx=(t.vx+s)*Math.random(),t.vy=(t.vy+i)*Math.random(),t.x+=t.vx,t.y+=t.vy}this.ctx.fillStyle=this.config.particleColor;const t=this.config.pointSize*this.DPR;for(const s of this.particles)this.ctx.beginPath(),this.ctx.arc(s.x,s.y,t,0,2*Math.PI),this.ctx.fill();this.config.onUpdate&&this.config.onUpdate(this)}animate(){this.isRunning&&(this.update(),this.animationId=requestAnimationFrame(()=>this.animate()))}start(){this.isRunning||(this.isRunning=!0,this.animate())}stop(){this.isRunning=!1,this.animationId&&(cancelAnimationFrame(this.animationId),this.animationId=null)}setupEventListeners(){window.addEventListener("resize",this.handleResize),this.canvas.addEventListener("mousemove",this.handleMouseMove),this.canvas.addEventListener("mouseleave",this.handleMouseLeave),this.canvas.addEventListener("mousedown",this.handleMouseDown),window.addEventListener("mouseup",this.handleMouseUp)}removeEventListeners(){window.removeEventListener("resize",this.handleResize),this.canvas.removeEventListener("mousemove",this.handleMouseMove),this.canvas.removeEventListener("mouseleave",this.handleMouseLeave),this.canvas.removeEventListener("mousedown",this.handleMouseDown),window.removeEventListener("mouseup",this.handleMouseUp)}handleResize(){this.resize()}handleMouseMove(t){const s=this.canvas.getBoundingClientRect();this.mouse.x=(t.clientX-s.left)*this.DPR,this.mouse.y=(t.clientY-s.top)*this.DPR}handleMouseLeave(){this.mouse.x=this.mouse.y=0}handleMouseDown(){this.mouse.down=!0}handleMouseUp(){this.mouse.down=!1}updateConfig(t){this.config={...this.config,...t},t.text&&this.buildTargets()}destroy(){this.stop(),this.removeEventListeners(),this.canvas&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas)}}export{t as MasonEffect,t as default};
1
+ function t(t,s){let i;return function(...h){clearTimeout(i),i=setTimeout(()=>{clearTimeout(i),t.apply(this,h)},s)}}class s{constructor(s,i={}){if(this.container="string"==typeof s?document.querySelector(s):s,!this.container)throw Error("Container element not found");this.config={text:i.text||"mason effect",densityStep:i.densityStep??2,maxParticles:i.maxParticles??3200,pointSize:i.pointSize??.5,ease:i.ease??.05,repelRadius:i.repelRadius??150,repelStrength:i.repelStrength??1,particleColor:i.particleColor||"#fff",fontFamily:i.fontFamily||"Inter, system-ui, Arial",fontSize:i.fontSize||null,width:i.width||null,height:i.height||null,devicePixelRatio:i.devicePixelRatio??null,onReady:i.onReady||null,onUpdate:i.onUpdate||null},this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.container.appendChild(this.canvas),this.canvas.style.display="block",this.offCanvas=document.createElement("canvas"),this.offCtx=this.offCanvas.getContext("2d"),this.W=0,this.H=0,this.DPR=this.config.devicePixelRatio||Math.min(window.devicePixelRatio||1,1.8),this.particles=[],this.mouse={x:0,y:0,down:!1},this.animationId=null,this.isRunning=!1,this.isVisible=!1,this.intersectionObserver=null,this.debounceDelay=i.debounceDelay??150;const h=this.handleResize.bind(this);this.handleResize=t(h,this.debounceDelay),this.handleMouseMove=this.handleMouseMove.bind(this),this.handleMouseLeave=this.handleMouseLeave.bind(this),this.handleMouseDown=this.handleMouseDown.bind(this),this.handleMouseUp=this.handleMouseUp.bind(this),this.t=t(this.i.bind(this),this.debounceDelay),this.h=t(this.o.bind(this),this.debounceDelay),this.init()}init(){this.resize(),this.setupEventListeners(),this.setupIntersectionObserver(),this.config.onReady&&this.config.onReady(this)}setupIntersectionObserver(){if("undefined"==typeof window||void 0===window.IntersectionObserver)return this.isVisible=!0,void this.start();this.intersectionObserver||(this.intersectionObserver=new IntersectionObserver(t=>{for(const s of t)s.target===this.container&&(s.isIntersecting?(this.isVisible=!0,this.start()):(this.isVisible=!1,this.stop()))},{threshold:.1}),this.intersectionObserver.observe(this.container))}resize(){const t=this.config.width||this.container.clientWidth||window.innerWidth,s=this.config.height||this.container.clientHeight||.7*window.innerHeight;this.W=Math.floor(t*this.DPR),this.H=Math.floor(s*this.DPR),this.canvas.width=this.W,this.canvas.height=this.H,this.canvas.style.width=t+"px",this.canvas.style.height=s+"px",this.buildTargets(),this.particles.length||this.initParticles()}buildTargets(){const t=this.config.text;this.offCanvas.width=this.W,this.offCanvas.height=this.H,this.offCtx.clearRect(0,0,this.offCanvas.width,this.offCanvas.height);const s=Math.min(this.W,this.H),i=this.config.fontSize||Math.max(80,Math.floor(.18*s));this.offCtx.fillStyle="#ffffff",this.offCtx.textAlign="center",this.offCtx.textBaseline="middle",this.offCtx.font=`400 ${i}px ${this.config.fontFamily}`;const h=t.split(""),e=.05*i,o=this.offCtx.measureText(t).width+e*(h.length-1);let n=this.W/2-o/2;for(const t of h)this.offCtx.fillText(t,n+this.offCtx.measureText(t).width/2,this.H/2),n+=this.offCtx.measureText(t).width+e;const a=Math.max(2,this.config.densityStep),l=this.offCtx.getImageData(0,0,this.W,this.H).data,r=[];for(let t=0;t<this.H;t+=a)for(let s=0;s<this.W;s+=a){const i=4*(t*this.W+s);l[i]+l[i+1]+l[i+2]>600&&r.push({x:s,y:t})}for(;r.length>this.config.maxParticles;)r.splice(Math.floor(Math.random()*r.length),1);if(this.particles.length<r.length){const t=r.length-this.particles.length;for(let s=0;t>s;s++)this.particles.push(this.makeParticle())}else this.particles.length>r.length&&(this.particles.length=r.length);for(let t=0;t<this.particles.length;t++){const s=this.particles[t],i=r[t];s.tx=i.x,s.ty=i.y}}makeParticle(){const t=Math.random()*this.W,s=Math.random()*this.H;return{x:t,y:s,vx:0,vy:0,tx:t,ty:s,initialX:t,initialY:s,j:Math.random()*Math.PI*2}}initParticles(){for(const t of this.particles){const s=Math.random()*this.W,i=Math.random()*this.H;t.x=s,t.y=i,t.vx=t.vy=0,t.initialX=s,t.initialY=i}}scatter(){for(const t of this.particles)void 0!==t.initialX&&void 0!==t.initialY?(t.tx=t.initialX,t.ty=t.initialY):(t.initialX=t.x,t.initialY=t.y,t.tx=t.initialX,t.ty=t.initialY)}morph(t=null){this.t(t)}i(t=null){if(0!==this.W&&0!==this.H||this.resize(),"string"==typeof t)this.config.text=t,this.buildTargets();else if(t&&"object"==typeof t){const s=void 0!==t.text;this.config={...this.config,...t},s&&this.buildTargets()}else this.buildTargets()}update(){this.ctx.clearRect(0,0,this.W,this.H);for(const t of this.particles){let s=(t.tx-t.x)*this.config.ease,i=(t.ty-t.y)*this.config.ease;if(this.mouse.x||this.mouse.y){const h=t.x-this.mouse.x,e=t.y-this.mouse.y,o=h*h+e*e,n=this.config.repelRadius*this.DPR;if(n*n>o){const t=Math.sqrt(o)+1e-4,a=(this.mouse.down?-1:1)*this.config.repelStrength*(1-t/n);s+=h/t*a*6,i+=e/t*a*6}}t.j+=2,s+=.05*Math.cos(t.j),i+=.05*Math.sin(1.3*t.j),t.vx=(t.vx+s)*Math.random(),t.vy=(t.vy+i)*Math.random(),t.x+=t.vx,t.y+=t.vy}this.ctx.fillStyle=this.config.particleColor;const t=this.config.pointSize*this.DPR;for(const s of this.particles)this.ctx.beginPath(),this.ctx.arc(s.x,s.y,t,0,2*Math.PI),this.ctx.fill();this.config.onUpdate&&this.config.onUpdate(this)}animate(){this.isRunning&&(this.update(),this.animationId=requestAnimationFrame(()=>this.animate()))}start(){this.isRunning||(this.isRunning=!0,this.animate())}stop(){this.isRunning=!1,this.animationId&&(cancelAnimationFrame(this.animationId),this.animationId=null)}setupEventListeners(){window.addEventListener("resize",this.handleResize),this.canvas.addEventListener("mousemove",this.handleMouseMove),this.canvas.addEventListener("mouseleave",this.handleMouseLeave),this.canvas.addEventListener("mousedown",this.handleMouseDown),window.addEventListener("mouseup",this.handleMouseUp)}removeEventListeners(){window.removeEventListener("resize",this.handleResize),this.canvas.removeEventListener("mousemove",this.handleMouseMove),this.canvas.removeEventListener("mouseleave",this.handleMouseLeave),this.canvas.removeEventListener("mousedown",this.handleMouseDown),window.removeEventListener("mouseup",this.handleMouseUp)}handleResize(){this.resize()}handleMouseMove(t){const s=this.canvas.getBoundingClientRect();this.mouse.x=(t.clientX-s.left)*this.DPR,this.mouse.y=(t.clientY-s.top)*this.DPR}handleMouseLeave(){this.mouse.x=this.mouse.y=0}handleMouseDown(){this.mouse.down=!0}handleMouseUp(){this.mouse.down=!1}updateConfig(t){this.h(t)}o(t){this.config={...this.config,...t},t.text&&this.buildTargets()}destroy(){this.stop(),this.removeEventListeners(),this.intersectionObserver&&(this.intersectionObserver.disconnect(),this.intersectionObserver=null),this.canvas&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas)}}export{s as MasonEffect,s as default};
package/dist/index.js CHANGED
@@ -7,6 +7,19 @@ Object.defineProperty(exports, '__esModule', { value: true });
7
7
  * 바닐라 JS 코어 클래스
8
8
  */
9
9
 
10
+ // 디바운스 유틸리티 함수
11
+ function debounce(func, wait) {
12
+ let timeout;
13
+ return function executedFunction(...args) {
14
+ const later = () => {
15
+ clearTimeout(timeout);
16
+ func.apply(this, args);
17
+ };
18
+ clearTimeout(timeout);
19
+ timeout = setTimeout(later, wait);
20
+ };
21
+ }
22
+
10
23
  class MasonEffect {
11
24
  constructor(container, options = {}) {
12
25
  // 컨테이너 요소
@@ -55,14 +68,24 @@ class MasonEffect {
55
68
  this.mouse = { x: 0, y: 0, down: false };
56
69
  this.animationId = null;
57
70
  this.isRunning = false;
71
+ this.isVisible = false;
72
+ this.intersectionObserver = null;
58
73
 
59
- // 이벤트 핸들러 바인딩
60
- this.handleResize = this.handleResize.bind(this);
74
+ // 디바운스 설정 (ms)
75
+ this.debounceDelay = options.debounceDelay ?? 150; // 기본 150ms
76
+
77
+ // 이벤트 핸들러 바인딩 (디바운스 적용 전에 바인딩)
78
+ const boundHandleResize = this.handleResize.bind(this);
79
+ this.handleResize = debounce(boundHandleResize, this.debounceDelay);
61
80
  this.handleMouseMove = this.handleMouseMove.bind(this);
62
81
  this.handleMouseLeave = this.handleMouseLeave.bind(this);
63
82
  this.handleMouseDown = this.handleMouseDown.bind(this);
64
83
  this.handleMouseUp = this.handleMouseUp.bind(this);
65
84
 
85
+ // morph와 updateConfig를 위한 디바운스된 내부 메서드
86
+ this._debouncedMorph = debounce(this._morphInternal.bind(this), this.debounceDelay);
87
+ this._debouncedUpdateConfig = debounce(this._updateConfigInternal.bind(this), this.debounceDelay);
88
+
66
89
  // 초기화
67
90
  this.init();
68
91
  }
@@ -70,13 +93,48 @@ class MasonEffect {
70
93
  init() {
71
94
  this.resize();
72
95
  this.setupEventListeners();
73
- this.start();
74
-
96
+ this.setupIntersectionObserver();
97
+
75
98
  if (this.config.onReady) {
76
99
  this.config.onReady(this);
77
100
  }
78
101
  }
79
102
 
103
+ setupIntersectionObserver() {
104
+ // IntersectionObserver가 지원되지 않는 환경에서는 항상 재생
105
+ if (typeof window === 'undefined' || typeof window.IntersectionObserver === 'undefined') {
106
+ this.isVisible = true;
107
+ this.start();
108
+ return;
109
+ }
110
+
111
+ // 이미 설정되어 있다면 다시 만들지 않음
112
+ if (this.intersectionObserver) {
113
+ return;
114
+ }
115
+
116
+ this.intersectionObserver = new IntersectionObserver(
117
+ (entries) => {
118
+ for (const entry of entries) {
119
+ if (entry.target !== this.container) continue;
120
+
121
+ if (entry.isIntersecting) {
122
+ this.isVisible = true;
123
+ this.start();
124
+ } else {
125
+ this.isVisible = false;
126
+ this.stop();
127
+ }
128
+ }
129
+ },
130
+ {
131
+ threshold: 0.1, // 10% 이상 보일 때 동작
132
+ }
133
+ );
134
+
135
+ this.intersectionObserver.observe(this.container);
136
+ }
137
+
80
138
  resize() {
81
139
  const width = this.config.width || this.container.clientWidth || window.innerWidth;
82
140
  const height = this.config.height || this.container.clientHeight || window.innerHeight * 0.7;
@@ -206,6 +264,12 @@ class MasonEffect {
206
264
  }
207
265
 
208
266
  morph(textOrOptions = null) {
267
+ // 즉시 실행이 필요한 경우 (예: 초기화 시)를 위해 내부 메서드 직접 호출
268
+ // 일반적인 경우에는 디바운스 적용
269
+ this._debouncedMorph(textOrOptions);
270
+ }
271
+
272
+ _morphInternal(textOrOptions = null) {
209
273
  // W와 H가 0이면 resize 먼저 실행
210
274
  if (this.W === 0 || this.H === 0) {
211
275
  this.resize();
@@ -336,6 +400,11 @@ class MasonEffect {
336
400
 
337
401
  // 설정 업데이트
338
402
  updateConfig(newConfig) {
403
+ // 디바운스 적용
404
+ this._debouncedUpdateConfig(newConfig);
405
+ }
406
+
407
+ _updateConfigInternal(newConfig) {
339
408
  this.config = { ...this.config, ...newConfig };
340
409
  if (newConfig.text) {
341
410
  this.buildTargets();
@@ -346,6 +415,10 @@ class MasonEffect {
346
415
  destroy() {
347
416
  this.stop();
348
417
  this.removeEventListeners();
418
+ if (this.intersectionObserver) {
419
+ this.intersectionObserver.disconnect();
420
+ this.intersectionObserver = null;
421
+ }
349
422
  if (this.canvas && this.canvas.parentNode) {
350
423
  this.canvas.parentNode.removeChild(this.canvas);
351
424
  }