falling-animation 0.1.0
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/LICENSE +21 -0
- package/README.md +251 -0
- package/dist/falling-animation.cjs.js +670 -0
- package/dist/falling-animation.cjs.js.map +1 -0
- package/dist/falling-animation.esm.js +663 -0
- package/dist/falling-animation.esm.js.map +1 -0
- package/dist/falling-animation.umd.js +676 -0
- package/dist/falling-animation.umd.js.map +1 -0
- package/dist/falling-animation.umd.min.js +9 -0
- package/dist/falling-animation.umd.min.js.map +1 -0
- package/dist/types/FallingAnimation.d.ts +77 -0
- package/dist/types/Particle.d.ts +52 -0
- package/dist/types/animations/index.d.ts +45 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/types.d.ts +100 -0
- package/dist/types/utils.d.ts +51 -0
- package/package.json +46 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility functions for falling-animation
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Generate a random number between min and max
|
|
10
|
+
*/
|
|
11
|
+
function randomRange(min, max) {
|
|
12
|
+
return Math.random() * (max - min) + min;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate a random number from a RangeValue
|
|
16
|
+
*/
|
|
17
|
+
function randomFromRange(range) {
|
|
18
|
+
return randomRange(range.min, range.max);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Pick a random item from an array
|
|
22
|
+
*/
|
|
23
|
+
function randomPick(array) {
|
|
24
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pick a random item based on weights
|
|
28
|
+
*/
|
|
29
|
+
function weightedRandomPick(items) {
|
|
30
|
+
var _a;
|
|
31
|
+
const totalWeight = items.reduce((sum, item) => { var _a; return sum + ((_a = item.weight) !== null && _a !== void 0 ? _a : 1); }, 0);
|
|
32
|
+
let random = Math.random() * totalWeight;
|
|
33
|
+
for (const item of items) {
|
|
34
|
+
random -= (_a = item.weight) !== null && _a !== void 0 ? _a : 1;
|
|
35
|
+
if (random <= 0) {
|
|
36
|
+
return item;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return items[items.length - 1];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate a unique ID
|
|
43
|
+
*/
|
|
44
|
+
let idCounter = 0;
|
|
45
|
+
function generateId() {
|
|
46
|
+
return ++idCounter;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve container from element or selector
|
|
50
|
+
*/
|
|
51
|
+
function resolveContainer(container) {
|
|
52
|
+
if (!container) {
|
|
53
|
+
return document.body;
|
|
54
|
+
}
|
|
55
|
+
if (typeof container === 'string') {
|
|
56
|
+
const element = document.querySelector(container);
|
|
57
|
+
if (!element) {
|
|
58
|
+
console.warn(`[falling-animation] Container "${container}" not found, using document.body`);
|
|
59
|
+
return document.body;
|
|
60
|
+
}
|
|
61
|
+
return element;
|
|
62
|
+
}
|
|
63
|
+
return container;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Throttle function calls
|
|
67
|
+
*/
|
|
68
|
+
function throttle(func, limit) {
|
|
69
|
+
let inThrottle = false;
|
|
70
|
+
return function (...args) {
|
|
71
|
+
if (!inThrottle) {
|
|
72
|
+
func.apply(this, args);
|
|
73
|
+
inThrottle = true;
|
|
74
|
+
setTimeout(() => (inThrottle = false), limit);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Animation functions for falling particles
|
|
81
|
+
* Each animation modifies particle state based on deltaTime and elapsed time
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* Simple vertical fall - no special effects
|
|
85
|
+
*/
|
|
86
|
+
const fall = (particle, deltaTime) => {
|
|
87
|
+
// Skip movement if speed is 0
|
|
88
|
+
if (particle.vy === 0 && particle.vx === 0)
|
|
89
|
+
return;
|
|
90
|
+
particle.y += particle.vy * deltaTime;
|
|
91
|
+
particle.x += particle.vx * deltaTime;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Swing - pendulum-like swinging motion while falling
|
|
95
|
+
*/
|
|
96
|
+
const swing = (particle, deltaTime, elapsed) => {
|
|
97
|
+
const swingAmplitude = 30; // pixels
|
|
98
|
+
const swingFrequency = 0.002; // oscillations per ms
|
|
99
|
+
// Slight rotation based on swing - always animate
|
|
100
|
+
particle.rotation = Math.sin((elapsed + particle.phase) * swingFrequency) * 20;
|
|
101
|
+
// Skip movement if speed is 0
|
|
102
|
+
if (particle.vy === 0 && particle.vx === 0)
|
|
103
|
+
return;
|
|
104
|
+
particle.y += particle.vy * deltaTime;
|
|
105
|
+
// Calculate swing offset
|
|
106
|
+
const swingOffset = Math.sin((elapsed + particle.phase) * swingFrequency) * swingAmplitude;
|
|
107
|
+
particle.x += particle.vx * deltaTime + (swingOffset * 0.05);
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Rotate - continuous 360° rotation while falling
|
|
111
|
+
*/
|
|
112
|
+
const rotate = (particle, deltaTime) => {
|
|
113
|
+
// Continuous rotation - always animate
|
|
114
|
+
particle.rotation += particle.rotationSpeed * deltaTime;
|
|
115
|
+
// Skip movement if speed is 0
|
|
116
|
+
if (particle.vy === 0 && particle.vx === 0)
|
|
117
|
+
return;
|
|
118
|
+
particle.y += particle.vy * deltaTime;
|
|
119
|
+
particle.x += particle.vx * deltaTime;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Flutter - butterfly-like fluttering motion
|
|
123
|
+
*/
|
|
124
|
+
const flutter = (particle, deltaTime, elapsed) => {
|
|
125
|
+
const flutterFrequency = 0.005;
|
|
126
|
+
// Tilt based on direction - always animate
|
|
127
|
+
particle.rotation = Math.sin((elapsed + particle.phase) * flutterFrequency) * 30;
|
|
128
|
+
// Skip movement if speed is 0
|
|
129
|
+
if (particle.vy === 0 && particle.vx === 0)
|
|
130
|
+
return;
|
|
131
|
+
const flutterAmplitude = 40;
|
|
132
|
+
const verticalWobble = 0.3;
|
|
133
|
+
// Horizontal flutter
|
|
134
|
+
const flutter = Math.sin((elapsed + particle.phase) * flutterFrequency) * flutterAmplitude;
|
|
135
|
+
particle.x += particle.vx * deltaTime + flutter * 0.03;
|
|
136
|
+
// Vertical movement with slight wobble
|
|
137
|
+
const wobble = Math.sin((elapsed + particle.phase) * flutterFrequency * 2) * verticalWobble;
|
|
138
|
+
particle.y += (particle.vy + wobble) * deltaTime;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Spiral - spiraling down pattern
|
|
142
|
+
*/
|
|
143
|
+
const spiral = (particle, deltaTime, elapsed) => {
|
|
144
|
+
const spiralSpeed = 0.003;
|
|
145
|
+
const angle = (elapsed + particle.phase) * spiralSpeed;
|
|
146
|
+
// Rotate with spiral - always animate
|
|
147
|
+
particle.rotation = angle * (180 / Math.PI);
|
|
148
|
+
// Skip movement if speed is 0
|
|
149
|
+
if (particle.vy === 0 && particle.vx === 0)
|
|
150
|
+
return;
|
|
151
|
+
const spiralRadius = 25;
|
|
152
|
+
particle.y += particle.vy * deltaTime;
|
|
153
|
+
// Spiral motion
|
|
154
|
+
particle.x += particle.vx * deltaTime + Math.cos(angle) * spiralRadius * 0.02;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Tumble - chaotic tumbling motion
|
|
158
|
+
*/
|
|
159
|
+
const tumble = (particle, deltaTime, elapsed) => {
|
|
160
|
+
var _a, _b, _c;
|
|
161
|
+
const tumbleSpeed = (_a = particle.data.tumbleSpeed) !== null && _a !== void 0 ? _a : 5;
|
|
162
|
+
// Fast tumbling rotation - always animate
|
|
163
|
+
particle.rotation += tumbleSpeed * deltaTime;
|
|
164
|
+
// Skip movement if speed is 0
|
|
165
|
+
if (particle.vy === 0 && particle.vx === 0)
|
|
166
|
+
return;
|
|
167
|
+
const wobbleX = (_b = particle.data.wobbleX) !== null && _b !== void 0 ? _b : 0.02;
|
|
168
|
+
const wobbleY = (_c = particle.data.wobbleY) !== null && _c !== void 0 ? _c : 0.01;
|
|
169
|
+
particle.y += particle.vy * deltaTime;
|
|
170
|
+
// Chaotic horizontal movement
|
|
171
|
+
particle.x += particle.vx * deltaTime +
|
|
172
|
+
Math.sin((elapsed + particle.phase) * wobbleX) * 20 * 0.02;
|
|
173
|
+
// Add some vertical variation
|
|
174
|
+
particle.y += Math.sin((elapsed + particle.phase * 1.5) * wobbleY) * 0.5;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Zigzag - zigzag falling pattern
|
|
178
|
+
*/
|
|
179
|
+
const zigzag = (particle, deltaTime, elapsed) => {
|
|
180
|
+
const zigzagFrequency = 0.002;
|
|
181
|
+
const period = 1 / zigzagFrequency;
|
|
182
|
+
const t = ((elapsed + particle.phase) % period) / period;
|
|
183
|
+
const triangleWave = Math.abs(t * 2 - 1) * 2 - 1;
|
|
184
|
+
// Tilt in direction of movement - always animate
|
|
185
|
+
particle.rotation = triangleWave * 25;
|
|
186
|
+
// Skip movement if speed is 0
|
|
187
|
+
if (particle.vy === 0 && particle.vx === 0)
|
|
188
|
+
return;
|
|
189
|
+
const zigzagWidth = 50;
|
|
190
|
+
particle.y += particle.vy * deltaTime;
|
|
191
|
+
// Create zigzag using triangle wave
|
|
192
|
+
particle.x += particle.vx * deltaTime + triangleWave * zigzagWidth * 0.02;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Float - slow floating descent with gentle movement
|
|
196
|
+
*/
|
|
197
|
+
const float = (particle, deltaTime, elapsed) => {
|
|
198
|
+
const floatFrequency = 0.001;
|
|
199
|
+
// Gentle rotation - always animate
|
|
200
|
+
particle.rotation = Math.sin((elapsed + particle.phase) * floatFrequency) * 10;
|
|
201
|
+
// Skip movement if speed is 0
|
|
202
|
+
if (particle.vy === 0 && particle.vx === 0)
|
|
203
|
+
return;
|
|
204
|
+
const floatAmplitudeX = 20;
|
|
205
|
+
const floatAmplitudeY = 5;
|
|
206
|
+
// Very slow descent with vertical wobble
|
|
207
|
+
const yWobble = Math.sin((elapsed + particle.phase) * floatFrequency * 2) * floatAmplitudeY;
|
|
208
|
+
particle.y += (particle.vy * 0.5 + yWobble * 0.01) * deltaTime;
|
|
209
|
+
// Gentle horizontal drift
|
|
210
|
+
const xDrift = Math.sin((elapsed + particle.phase) * floatFrequency) * floatAmplitudeX;
|
|
211
|
+
particle.x += particle.vx * deltaTime + xDrift * 0.01;
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Animation registry - all available animations
|
|
215
|
+
*/
|
|
216
|
+
const animations = {
|
|
217
|
+
fall,
|
|
218
|
+
swing,
|
|
219
|
+
rotate,
|
|
220
|
+
flutter,
|
|
221
|
+
spiral,
|
|
222
|
+
tumble,
|
|
223
|
+
zigzag,
|
|
224
|
+
float
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* Get animation function by name
|
|
228
|
+
*/
|
|
229
|
+
function getAnimation(name) {
|
|
230
|
+
var _a;
|
|
231
|
+
return (_a = animations[name]) !== null && _a !== void 0 ? _a : fall;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Particle class - manages individual falling objects
|
|
236
|
+
*/
|
|
237
|
+
/**
|
|
238
|
+
* Speed multiplier to convert user-friendly speed units to px/ms
|
|
239
|
+
* speed = 1 means ~40 pixels per second (~1cm/s at 96 DPI)
|
|
240
|
+
* speed = 0.1 means ~4 pixels per second (~1mm/s)
|
|
241
|
+
* speed = 0 means standing still
|
|
242
|
+
*/
|
|
243
|
+
const SPEED_MULTIPLIER = 0.04; // 40 px/s per unit, divided by 1000 for ms
|
|
244
|
+
class Particle {
|
|
245
|
+
constructor(options) {
|
|
246
|
+
this.options = options;
|
|
247
|
+
this.id = generateId();
|
|
248
|
+
this.age = 0;
|
|
249
|
+
this.phase = Math.random() * Math.PI * 2;
|
|
250
|
+
this.data = {};
|
|
251
|
+
// Get container dimensions
|
|
252
|
+
this.containerWidth = options.container.clientWidth;
|
|
253
|
+
this.containerHeight = options.container.clientHeight;
|
|
254
|
+
// Pick random object
|
|
255
|
+
const object = weightedRandomPick(options.objects);
|
|
256
|
+
// Initialize position at top with random x
|
|
257
|
+
this.x = randomRange(-50, this.containerWidth + 50);
|
|
258
|
+
this.y = -50;
|
|
259
|
+
// Initialize velocity (apply speed multiplier for intuitive units)
|
|
260
|
+
// speed=1 → ~40px/s (1cm/s), speed=0.1 → ~4px/s (1mm/s)
|
|
261
|
+
this.vy = randomFromRange(options.speed) * SPEED_MULTIPLIER;
|
|
262
|
+
this.vx = options.wind * SPEED_MULTIPLIER * randomRange(0.5, 1.5);
|
|
263
|
+
// Initialize size and opacity
|
|
264
|
+
this.size = randomFromRange(options.size);
|
|
265
|
+
this.opacity = randomFromRange(options.opacity);
|
|
266
|
+
// Initialize rotation
|
|
267
|
+
this.rotation = randomRange(0, 360);
|
|
268
|
+
this.rotationSpeed = randomRange(-3, 3);
|
|
269
|
+
// Pick animation
|
|
270
|
+
this.animation = randomPick(options.animation);
|
|
271
|
+
// Initialize animation-specific data
|
|
272
|
+
this.initAnimationData();
|
|
273
|
+
// Create DOM element
|
|
274
|
+
this.element = this.createElement(object);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Initialize animation-specific data
|
|
278
|
+
*/
|
|
279
|
+
initAnimationData() {
|
|
280
|
+
if (this.animation === 'tumble') {
|
|
281
|
+
this.data.tumbleSpeed = randomRange(3, 8);
|
|
282
|
+
this.data.wobbleX = randomRange(0.01, 0.03);
|
|
283
|
+
this.data.wobbleY = randomRange(0.005, 0.015);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Create DOM element for the particle
|
|
288
|
+
*/
|
|
289
|
+
createElement(object) {
|
|
290
|
+
var _a, _b, _c, _d;
|
|
291
|
+
const element = document.createElement('div');
|
|
292
|
+
element.className = 'falling-particle';
|
|
293
|
+
element.setAttribute('data-particle-id', String(this.id));
|
|
294
|
+
// Set content based on type
|
|
295
|
+
switch (object.type) {
|
|
296
|
+
case 'emoji':
|
|
297
|
+
element.textContent = (_a = object.content) !== null && _a !== void 0 ? _a : '❄️';
|
|
298
|
+
element.style.fontSize = `${this.size}px`;
|
|
299
|
+
element.style.lineHeight = '1';
|
|
300
|
+
break;
|
|
301
|
+
case 'image':
|
|
302
|
+
const img = document.createElement('img');
|
|
303
|
+
img.src = (_c = (_b = object.src) !== null && _b !== void 0 ? _b : object.content) !== null && _c !== void 0 ? _c : '';
|
|
304
|
+
img.alt = '';
|
|
305
|
+
img.style.width = `${this.size}px`;
|
|
306
|
+
img.style.height = `${this.size}px`;
|
|
307
|
+
img.style.objectFit = 'contain';
|
|
308
|
+
img.draggable = false;
|
|
309
|
+
element.appendChild(img);
|
|
310
|
+
break;
|
|
311
|
+
case 'html':
|
|
312
|
+
element.innerHTML = (_d = object.content) !== null && _d !== void 0 ? _d : '';
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
// Apply base styles
|
|
316
|
+
Object.assign(element.style, {
|
|
317
|
+
position: 'absolute',
|
|
318
|
+
pointerEvents: 'none',
|
|
319
|
+
userSelect: 'none',
|
|
320
|
+
willChange: 'transform, opacity',
|
|
321
|
+
opacity: String(this.opacity),
|
|
322
|
+
left: '0',
|
|
323
|
+
top: '0',
|
|
324
|
+
transform: this.getTransform()
|
|
325
|
+
});
|
|
326
|
+
return element;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get CSS transform string
|
|
330
|
+
*/
|
|
331
|
+
getTransform() {
|
|
332
|
+
return `translate3d(${this.x}px, ${this.y}px, 0) rotate(${this.rotation}deg)`;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Update particle state
|
|
336
|
+
*/
|
|
337
|
+
update(deltaTime, elapsed) {
|
|
338
|
+
this.age += deltaTime;
|
|
339
|
+
// Apply animation
|
|
340
|
+
const animationFn = getAnimation(this.animation);
|
|
341
|
+
animationFn(this, deltaTime, elapsed);
|
|
342
|
+
// Update DOM
|
|
343
|
+
this.element.style.transform = this.getTransform();
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Check if particle is out of bounds
|
|
347
|
+
*/
|
|
348
|
+
isOutOfBounds() {
|
|
349
|
+
return (this.y > this.containerHeight + 100 ||
|
|
350
|
+
this.x < -100 ||
|
|
351
|
+
this.x > this.containerWidth + 100);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Update container dimensions (for resize handling)
|
|
355
|
+
*/
|
|
356
|
+
updateContainerSize(width, height) {
|
|
357
|
+
this.containerWidth = width;
|
|
358
|
+
this.containerHeight = height;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Remove particle from DOM
|
|
362
|
+
*/
|
|
363
|
+
destroy() {
|
|
364
|
+
this.element.remove();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* FallingAnimation - Main class for creating falling object animations
|
|
370
|
+
*/
|
|
371
|
+
/** Default configuration values */
|
|
372
|
+
const DEFAULTS = {
|
|
373
|
+
speed: { min: 2, max: 5 },
|
|
374
|
+
spawnRate: 3,
|
|
375
|
+
maxParticles: 50,
|
|
376
|
+
animation: ['fall'],
|
|
377
|
+
size: { min: 20, max: 40 },
|
|
378
|
+
opacity: { min: 0.6, max: 1 },
|
|
379
|
+
wind: 0,
|
|
380
|
+
autoStart: true,
|
|
381
|
+
zIndex: 9999,
|
|
382
|
+
responsive: true
|
|
383
|
+
};
|
|
384
|
+
class FallingAnimation {
|
|
385
|
+
constructor(options) {
|
|
386
|
+
this.particles = [];
|
|
387
|
+
this.wrapper = null;
|
|
388
|
+
this.isRunning = false;
|
|
389
|
+
this.isPaused = false;
|
|
390
|
+
this.animationId = null;
|
|
391
|
+
this.lastSpawnTime = 0;
|
|
392
|
+
this.startTime = 0;
|
|
393
|
+
this.lastFrameTime = 0;
|
|
394
|
+
this.resizeHandler = null;
|
|
395
|
+
/**
|
|
396
|
+
* Main animation loop
|
|
397
|
+
*/
|
|
398
|
+
this.animate = (currentTime) => {
|
|
399
|
+
if (!this.isRunning)
|
|
400
|
+
return;
|
|
401
|
+
// Calculate delta time
|
|
402
|
+
if (this.lastFrameTime === 0) {
|
|
403
|
+
this.lastFrameTime = currentTime;
|
|
404
|
+
this.startTime = currentTime;
|
|
405
|
+
}
|
|
406
|
+
const deltaTime = Math.min(currentTime - this.lastFrameTime, 50); // Cap at 50ms
|
|
407
|
+
const elapsed = currentTime - this.startTime;
|
|
408
|
+
this.lastFrameTime = currentTime;
|
|
409
|
+
// Spawn new particles based on rate
|
|
410
|
+
const spawnInterval = 1000 / this.options.spawnRate;
|
|
411
|
+
if (currentTime - this.lastSpawnTime >= spawnInterval) {
|
|
412
|
+
this.spawnParticle();
|
|
413
|
+
this.lastSpawnTime = currentTime;
|
|
414
|
+
}
|
|
415
|
+
// Update particles
|
|
416
|
+
const particlesToRemove = [];
|
|
417
|
+
for (const particle of this.particles) {
|
|
418
|
+
particle.update(deltaTime, elapsed);
|
|
419
|
+
if (particle.isOutOfBounds()) {
|
|
420
|
+
particlesToRemove.push(particle);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Remove out-of-bounds particles
|
|
424
|
+
particlesToRemove.forEach(p => this.removeParticle(p));
|
|
425
|
+
// Continue animation loop
|
|
426
|
+
this.animationId = requestAnimationFrame(this.animate);
|
|
427
|
+
};
|
|
428
|
+
// Validate required options
|
|
429
|
+
if (!options.objects || options.objects.length === 0) {
|
|
430
|
+
throw new Error('[falling-animation] "objects" option is required and must not be empty');
|
|
431
|
+
}
|
|
432
|
+
// Resolve options with defaults
|
|
433
|
+
this.options = this.resolveOptions(options);
|
|
434
|
+
// Create wrapper element
|
|
435
|
+
this.createWrapper();
|
|
436
|
+
// Setup resize handler if responsive
|
|
437
|
+
if (this.options.responsive) {
|
|
438
|
+
this.setupResizeHandler();
|
|
439
|
+
}
|
|
440
|
+
// Auto start if enabled
|
|
441
|
+
if (this.options.autoStart) {
|
|
442
|
+
this.start();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Merge user options with defaults
|
|
447
|
+
*/
|
|
448
|
+
resolveOptions(options) {
|
|
449
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
450
|
+
const container = resolveContainer(options.container);
|
|
451
|
+
// Normalize animation to array
|
|
452
|
+
let animation;
|
|
453
|
+
if (!options.animation) {
|
|
454
|
+
animation = DEFAULTS.animation;
|
|
455
|
+
}
|
|
456
|
+
else if (typeof options.animation === 'string') {
|
|
457
|
+
animation = [options.animation];
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
animation = options.animation;
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
container,
|
|
464
|
+
objects: options.objects,
|
|
465
|
+
speed: (_a = options.speed) !== null && _a !== void 0 ? _a : DEFAULTS.speed,
|
|
466
|
+
spawnRate: (_b = options.spawnRate) !== null && _b !== void 0 ? _b : DEFAULTS.spawnRate,
|
|
467
|
+
maxParticles: (_c = options.maxParticles) !== null && _c !== void 0 ? _c : DEFAULTS.maxParticles,
|
|
468
|
+
animation,
|
|
469
|
+
size: (_d = options.size) !== null && _d !== void 0 ? _d : DEFAULTS.size,
|
|
470
|
+
opacity: (_e = options.opacity) !== null && _e !== void 0 ? _e : DEFAULTS.opacity,
|
|
471
|
+
wind: (_f = options.wind) !== null && _f !== void 0 ? _f : DEFAULTS.wind,
|
|
472
|
+
autoStart: (_g = options.autoStart) !== null && _g !== void 0 ? _g : DEFAULTS.autoStart,
|
|
473
|
+
zIndex: (_h = options.zIndex) !== null && _h !== void 0 ? _h : DEFAULTS.zIndex,
|
|
474
|
+
responsive: (_j = options.responsive) !== null && _j !== void 0 ? _j : DEFAULTS.responsive
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Create wrapper element for particles
|
|
479
|
+
*/
|
|
480
|
+
createWrapper() {
|
|
481
|
+
this.wrapper = document.createElement('div');
|
|
482
|
+
this.wrapper.className = 'falling-animation-wrapper';
|
|
483
|
+
// Apply styles
|
|
484
|
+
Object.assign(this.wrapper.style, {
|
|
485
|
+
position: this.options.container === document.body ? 'fixed' : 'absolute',
|
|
486
|
+
top: '0',
|
|
487
|
+
left: '0',
|
|
488
|
+
width: '100%',
|
|
489
|
+
height: '100%',
|
|
490
|
+
overflow: 'hidden',
|
|
491
|
+
pointerEvents: 'none',
|
|
492
|
+
zIndex: String(this.options.zIndex)
|
|
493
|
+
});
|
|
494
|
+
// Ensure container has position for absolute wrapper
|
|
495
|
+
if (this.options.container !== document.body) {
|
|
496
|
+
const containerPosition = getComputedStyle(this.options.container).position;
|
|
497
|
+
if (containerPosition === 'static') {
|
|
498
|
+
this.options.container.style.position = 'relative';
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
this.options.container.appendChild(this.wrapper);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Setup resize handler for responsive behavior
|
|
505
|
+
*/
|
|
506
|
+
setupResizeHandler() {
|
|
507
|
+
this.resizeHandler = throttle(() => {
|
|
508
|
+
const width = this.options.container.clientWidth;
|
|
509
|
+
const height = this.options.container.clientHeight;
|
|
510
|
+
this.particles.forEach(particle => {
|
|
511
|
+
particle.updateContainerSize(width, height);
|
|
512
|
+
});
|
|
513
|
+
}, 200);
|
|
514
|
+
window.addEventListener('resize', this.resizeHandler);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Spawn a new particle
|
|
518
|
+
*/
|
|
519
|
+
spawnParticle() {
|
|
520
|
+
if (this.particles.length >= this.options.maxParticles) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const particle = new Particle(this.options);
|
|
524
|
+
this.particles.push(particle);
|
|
525
|
+
if (this.wrapper) {
|
|
526
|
+
this.wrapper.appendChild(particle.element);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Remove a particle
|
|
531
|
+
*/
|
|
532
|
+
removeParticle(particle) {
|
|
533
|
+
const index = this.particles.indexOf(particle);
|
|
534
|
+
if (index > -1) {
|
|
535
|
+
this.particles.splice(index, 1);
|
|
536
|
+
particle.destroy();
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Start the animation
|
|
541
|
+
*/
|
|
542
|
+
start() {
|
|
543
|
+
if (this.isRunning)
|
|
544
|
+
return;
|
|
545
|
+
this.isRunning = true;
|
|
546
|
+
this.isPaused = false;
|
|
547
|
+
this.lastFrameTime = 0;
|
|
548
|
+
this.lastSpawnTime = 0;
|
|
549
|
+
this.animationId = requestAnimationFrame(this.animate);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Stop the animation and clear all particles
|
|
553
|
+
*/
|
|
554
|
+
stop() {
|
|
555
|
+
this.isRunning = false;
|
|
556
|
+
this.isPaused = false;
|
|
557
|
+
if (this.animationId !== null) {
|
|
558
|
+
cancelAnimationFrame(this.animationId);
|
|
559
|
+
this.animationId = null;
|
|
560
|
+
}
|
|
561
|
+
// Clear all particles
|
|
562
|
+
this.particles.forEach(p => p.destroy());
|
|
563
|
+
this.particles = [];
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Pause the animation (keeps particles in place)
|
|
567
|
+
*/
|
|
568
|
+
pause() {
|
|
569
|
+
if (!this.isRunning || this.isPaused)
|
|
570
|
+
return;
|
|
571
|
+
this.isPaused = true;
|
|
572
|
+
this.isRunning = false;
|
|
573
|
+
if (this.animationId !== null) {
|
|
574
|
+
cancelAnimationFrame(this.animationId);
|
|
575
|
+
this.animationId = null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Resume a paused animation
|
|
580
|
+
*/
|
|
581
|
+
resume() {
|
|
582
|
+
if (this.isRunning || !this.isPaused)
|
|
583
|
+
return;
|
|
584
|
+
this.isRunning = true;
|
|
585
|
+
this.isPaused = false;
|
|
586
|
+
this.lastFrameTime = 0;
|
|
587
|
+
this.animationId = requestAnimationFrame(this.animate);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Update options dynamically
|
|
591
|
+
*/
|
|
592
|
+
setOptions(newOptions) {
|
|
593
|
+
// Merge new options
|
|
594
|
+
if (newOptions.speed) {
|
|
595
|
+
this.options.speed = newOptions.speed;
|
|
596
|
+
}
|
|
597
|
+
if (newOptions.spawnRate !== undefined) {
|
|
598
|
+
this.options.spawnRate = newOptions.spawnRate;
|
|
599
|
+
}
|
|
600
|
+
if (newOptions.maxParticles !== undefined) {
|
|
601
|
+
this.options.maxParticles = newOptions.maxParticles;
|
|
602
|
+
}
|
|
603
|
+
if (newOptions.animation) {
|
|
604
|
+
this.options.animation = typeof newOptions.animation === 'string'
|
|
605
|
+
? [newOptions.animation]
|
|
606
|
+
: newOptions.animation;
|
|
607
|
+
}
|
|
608
|
+
if (newOptions.size) {
|
|
609
|
+
this.options.size = newOptions.size;
|
|
610
|
+
}
|
|
611
|
+
if (newOptions.opacity) {
|
|
612
|
+
this.options.opacity = newOptions.opacity;
|
|
613
|
+
}
|
|
614
|
+
if (newOptions.wind !== undefined) {
|
|
615
|
+
this.options.wind = newOptions.wind;
|
|
616
|
+
}
|
|
617
|
+
if (newOptions.objects) {
|
|
618
|
+
this.options.objects = newOptions.objects;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get current particle count
|
|
623
|
+
*/
|
|
624
|
+
getParticleCount() {
|
|
625
|
+
return this.particles.length;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Check if animation is running
|
|
629
|
+
*/
|
|
630
|
+
getIsRunning() {
|
|
631
|
+
return this.isRunning;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Check if animation is paused
|
|
635
|
+
*/
|
|
636
|
+
getIsPaused() {
|
|
637
|
+
return this.isPaused;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Destroy the animation and clean up
|
|
641
|
+
*/
|
|
642
|
+
destroy() {
|
|
643
|
+
this.stop();
|
|
644
|
+
// Remove wrapper
|
|
645
|
+
if (this.wrapper) {
|
|
646
|
+
this.wrapper.remove();
|
|
647
|
+
this.wrapper = null;
|
|
648
|
+
}
|
|
649
|
+
// Remove resize handler
|
|
650
|
+
if (this.resizeHandler) {
|
|
651
|
+
window.removeEventListener('resize', this.resizeHandler);
|
|
652
|
+
this.resizeHandler = null;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* falling-animation
|
|
659
|
+
* A lightweight, customizable falling objects animation library
|
|
660
|
+
*
|
|
661
|
+
* @author phongdh
|
|
662
|
+
* @license MIT
|
|
663
|
+
*/
|
|
664
|
+
// Main class
|
|
665
|
+
|
|
666
|
+
exports.FallingAnimation = FallingAnimation;
|
|
667
|
+
exports.animations = animations;
|
|
668
|
+
exports.default = FallingAnimation;
|
|
669
|
+
exports.getAnimation = getAnimation;
|
|
670
|
+
//# sourceMappingURL=falling-animation.cjs.js.map
|