@sarthak03dot/romantic-animations 1.2.0 → 1.2.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.
- package/README.md +327 -327
- package/dist/romantic-animations.es.js.map +1 -1
- package/dist/romantic-animations.umd.js.map +1 -1
- package/package.json +53 -53
- package/src/animations/butterfly.js +92 -92
- package/src/animations/confetti.js +92 -92
- package/src/animations/fireworks.js +112 -112
- package/src/animations/floatingHearts.js +89 -89
- package/src/animations/floatingOrbs.js +76 -76
- package/src/animations/heartBurst.js +113 -113
- package/src/animations/heartTrail.js +85 -85
- package/src/animations/loveRain.js +71 -71
- package/src/animations/magicDust.js +87 -87
- package/src/animations/shootingStars.js +82 -82
- package/src/animations/sparkles.js +93 -93
- package/src/animations/starField.js +100 -100
- package/src/core/engine.js +77 -77
- package/src/index.js +224 -224
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import { mergeOptions } from '../core/engine.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULTS = {
|
|
4
|
-
density: 0.05, // Spawn probability per frame
|
|
5
|
-
colors: ['#c77dff', '#ff85a1', '#ffc2d1', '#48cae4', '#e7c6ff', '#fbb1bd'],
|
|
6
|
-
minSize: 10,
|
|
7
|
-
maxSize: 22,
|
|
8
|
-
minSpeed: 0.8,
|
|
9
|
-
maxSpeed: 2.2,
|
|
10
|
-
glow: true,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export function butterflies(canvas, userOptions = {}) {
|
|
14
|
-
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
15
|
-
const ctx = canvas.getContext('2d');
|
|
16
|
-
const items = [];
|
|
17
|
-
let running = true;
|
|
18
|
-
let time = 0;
|
|
19
|
-
|
|
20
|
-
function spawn() {
|
|
21
|
-
const size = opts.minSize + Math.random() * (opts.maxSize - opts.minSize);
|
|
22
|
-
return {
|
|
23
|
-
x: -size * 2,
|
|
24
|
-
y: canvas.height * 0.1 + Math.random() * (canvas.height * 0.8),
|
|
25
|
-
size,
|
|
26
|
-
speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
|
|
27
|
-
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
28
|
-
alpha: 0,
|
|
29
|
-
flapSpeed: 0.1 + Math.random() * 0.15,
|
|
30
|
-
flapOffset: Math.random() * Math.PI * 2,
|
|
31
|
-
wobbleSpeed: 0.01 + Math.random() * 0.02,
|
|
32
|
-
wobbleOffset: Math.random() * Math.PI * 2,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function animate() {
|
|
37
|
-
if (!running) return;
|
|
38
|
-
time++;
|
|
39
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
40
|
-
|
|
41
|
-
if (Math.random() < opts.density) items.push(spawn());
|
|
42
|
-
|
|
43
|
-
for (let i = items.length - 1; i >= 0; i--) {
|
|
44
|
-
const b = items[i];
|
|
45
|
-
b.x += b.speed;
|
|
46
|
-
b.y += Math.sin(time * b.wobbleSpeed + b.wobbleOffset) * 1.5;
|
|
47
|
-
|
|
48
|
-
// Fade in smoothly
|
|
49
|
-
if (b.alpha < 1 && b.x < canvas.width / 2) b.alpha += 0.02;
|
|
50
|
-
|
|
51
|
-
// Flapping wing calculation
|
|
52
|
-
const wingSpread = Math.abs(Math.sin(time * b.flapSpeed + b.flapOffset));
|
|
53
|
-
|
|
54
|
-
ctx.save();
|
|
55
|
-
ctx.globalAlpha = Math.min(1, b.alpha);
|
|
56
|
-
ctx.translate(b.x, b.y);
|
|
57
|
-
// Slight tilt upwards
|
|
58
|
-
ctx.rotate(-0.1 - (wingSpread * 0.1));
|
|
59
|
-
|
|
60
|
-
if (opts.glow) {
|
|
61
|
-
ctx.shadowColor = b.color;
|
|
62
|
-
ctx.shadowBlur = b.size * 1.5;
|
|
63
|
-
}
|
|
64
|
-
ctx.fillStyle = b.color;
|
|
65
|
-
|
|
66
|
-
// Draw wings
|
|
67
|
-
ctx.beginPath();
|
|
68
|
-
// Left/back wing (narrower based on wingSpread)
|
|
69
|
-
ctx.ellipse(-b.size * 0.2, 0, b.size * 0.4 * wingSpread, b.size * 0.5, 0.3, 0, Math.PI * 2);
|
|
70
|
-
ctx.fill();
|
|
71
|
-
|
|
72
|
-
ctx.beginPath();
|
|
73
|
-
// Right/front wing
|
|
74
|
-
ctx.ellipse(b.size * 0.3 * wingSpread, -b.size * 0.1, b.size * 0.5 * wingSpread, b.size * 0.6, -0.2, 0, Math.PI * 2);
|
|
75
|
-
ctx.fill();
|
|
76
|
-
ctx.restore();
|
|
77
|
-
|
|
78
|
-
if (b.x > canvas.width + b.size * 2) {
|
|
79
|
-
items.splice(i, 1);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
requestAnimationFrame(animate);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
animate();
|
|
87
|
-
|
|
88
|
-
return function stop() {
|
|
89
|
-
running = false;
|
|
90
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
91
|
-
};
|
|
92
|
-
}
|
|
1
|
+
import { mergeOptions } from '../core/engine.js';
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
density: 0.05, // Spawn probability per frame
|
|
5
|
+
colors: ['#c77dff', '#ff85a1', '#ffc2d1', '#48cae4', '#e7c6ff', '#fbb1bd'],
|
|
6
|
+
minSize: 10,
|
|
7
|
+
maxSize: 22,
|
|
8
|
+
minSpeed: 0.8,
|
|
9
|
+
maxSpeed: 2.2,
|
|
10
|
+
glow: true,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function butterflies(canvas, userOptions = {}) {
|
|
14
|
+
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
15
|
+
const ctx = canvas.getContext('2d');
|
|
16
|
+
const items = [];
|
|
17
|
+
let running = true;
|
|
18
|
+
let time = 0;
|
|
19
|
+
|
|
20
|
+
function spawn() {
|
|
21
|
+
const size = opts.minSize + Math.random() * (opts.maxSize - opts.minSize);
|
|
22
|
+
return {
|
|
23
|
+
x: -size * 2,
|
|
24
|
+
y: canvas.height * 0.1 + Math.random() * (canvas.height * 0.8),
|
|
25
|
+
size,
|
|
26
|
+
speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
|
|
27
|
+
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
28
|
+
alpha: 0,
|
|
29
|
+
flapSpeed: 0.1 + Math.random() * 0.15,
|
|
30
|
+
flapOffset: Math.random() * Math.PI * 2,
|
|
31
|
+
wobbleSpeed: 0.01 + Math.random() * 0.02,
|
|
32
|
+
wobbleOffset: Math.random() * Math.PI * 2,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function animate() {
|
|
37
|
+
if (!running) return;
|
|
38
|
+
time++;
|
|
39
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
40
|
+
|
|
41
|
+
if (Math.random() < opts.density) items.push(spawn());
|
|
42
|
+
|
|
43
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
44
|
+
const b = items[i];
|
|
45
|
+
b.x += b.speed;
|
|
46
|
+
b.y += Math.sin(time * b.wobbleSpeed + b.wobbleOffset) * 1.5;
|
|
47
|
+
|
|
48
|
+
// Fade in smoothly
|
|
49
|
+
if (b.alpha < 1 && b.x < canvas.width / 2) b.alpha += 0.02;
|
|
50
|
+
|
|
51
|
+
// Flapping wing calculation
|
|
52
|
+
const wingSpread = Math.abs(Math.sin(time * b.flapSpeed + b.flapOffset));
|
|
53
|
+
|
|
54
|
+
ctx.save();
|
|
55
|
+
ctx.globalAlpha = Math.min(1, b.alpha);
|
|
56
|
+
ctx.translate(b.x, b.y);
|
|
57
|
+
// Slight tilt upwards
|
|
58
|
+
ctx.rotate(-0.1 - (wingSpread * 0.1));
|
|
59
|
+
|
|
60
|
+
if (opts.glow) {
|
|
61
|
+
ctx.shadowColor = b.color;
|
|
62
|
+
ctx.shadowBlur = b.size * 1.5;
|
|
63
|
+
}
|
|
64
|
+
ctx.fillStyle = b.color;
|
|
65
|
+
|
|
66
|
+
// Draw wings
|
|
67
|
+
ctx.beginPath();
|
|
68
|
+
// Left/back wing (narrower based on wingSpread)
|
|
69
|
+
ctx.ellipse(-b.size * 0.2, 0, b.size * 0.4 * wingSpread, b.size * 0.5, 0.3, 0, Math.PI * 2);
|
|
70
|
+
ctx.fill();
|
|
71
|
+
|
|
72
|
+
ctx.beginPath();
|
|
73
|
+
// Right/front wing
|
|
74
|
+
ctx.ellipse(b.size * 0.3 * wingSpread, -b.size * 0.1, b.size * 0.5 * wingSpread, b.size * 0.6, -0.2, 0, Math.PI * 2);
|
|
75
|
+
ctx.fill();
|
|
76
|
+
ctx.restore();
|
|
77
|
+
|
|
78
|
+
if (b.x > canvas.width + b.size * 2) {
|
|
79
|
+
items.splice(i, 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
requestAnimationFrame(animate);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
animate();
|
|
87
|
+
|
|
88
|
+
return function stop() {
|
|
89
|
+
running = false;
|
|
90
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import { mergeOptions } from '../core/engine.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULTS = {
|
|
4
|
-
density: 0.18,
|
|
5
|
-
colors: ['#ff6b8a','#ff4d6d','#ffd6ff','#e7c6ff','#c77dff','#48cae4','#ffe66d','#06d6a0'],
|
|
6
|
-
minSize: 6,
|
|
7
|
-
maxSize: 14,
|
|
8
|
-
minSpeed: 1.5,
|
|
9
|
-
maxSpeed: 4,
|
|
10
|
-
gravity: 0.06,
|
|
11
|
-
drag: 0.99,
|
|
12
|
-
shapes: ['rect', 'circle', 'ribbon'],
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function confetti(canvas, userOptions = {}) {
|
|
16
|
-
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
17
|
-
const ctx = canvas.getContext('2d');
|
|
18
|
-
const pieces = [];
|
|
19
|
-
let running = true;
|
|
20
|
-
|
|
21
|
-
function createPiece() {
|
|
22
|
-
const size = opts.minSize + Math.random() * (opts.maxSize - opts.minSize);
|
|
23
|
-
const speed = opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed);
|
|
24
|
-
return {
|
|
25
|
-
x: Math.random() * canvas.width,
|
|
26
|
-
y: -size * 2,
|
|
27
|
-
w: size,
|
|
28
|
-
h: size * (0.4 + Math.random() * 0.8),
|
|
29
|
-
vx: (Math.random() - 0.5) * 3,
|
|
30
|
-
vy: speed,
|
|
31
|
-
angle: Math.random() * Math.PI * 2,
|
|
32
|
-
spin: (Math.random() - 0.5) * 0.15,
|
|
33
|
-
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
34
|
-
alpha: 0.8 + Math.random() * 0.2,
|
|
35
|
-
shape: opts.shapes[Math.floor(Math.random() * opts.shapes.length)],
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function drawPiece(p) {
|
|
40
|
-
ctx.save();
|
|
41
|
-
ctx.globalAlpha = p.alpha;
|
|
42
|
-
ctx.fillStyle = p.color;
|
|
43
|
-
ctx.strokeStyle = p.color;
|
|
44
|
-
ctx.translate(p.x, p.y);
|
|
45
|
-
ctx.rotate(p.angle);
|
|
46
|
-
|
|
47
|
-
if (p.shape === 'circle') {
|
|
48
|
-
ctx.beginPath();
|
|
49
|
-
ctx.ellipse(0, 0, p.w / 2, p.h / 2, 0, 0, Math.PI * 2);
|
|
50
|
-
ctx.fill();
|
|
51
|
-
} else if (p.shape === 'ribbon') {
|
|
52
|
-
ctx.beginPath();
|
|
53
|
-
ctx.moveTo(-p.w / 2, 0);
|
|
54
|
-
ctx.quadraticCurveTo(0, -p.h, p.w / 2, 0);
|
|
55
|
-
ctx.quadraticCurveTo(0, p.h, -p.w / 2, 0);
|
|
56
|
-
ctx.fill();
|
|
57
|
-
} else {
|
|
58
|
-
ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
|
|
59
|
-
}
|
|
60
|
-
ctx.restore();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function animate() {
|
|
64
|
-
if (!running) return;
|
|
65
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
66
|
-
|
|
67
|
-
if (Math.random() < opts.density) pieces.push(createPiece());
|
|
68
|
-
|
|
69
|
-
for (let i = pieces.length - 1; i >= 0; i--) {
|
|
70
|
-
const p = pieces[i];
|
|
71
|
-
p.vy += opts.gravity;
|
|
72
|
-
p.vx *= opts.drag;
|
|
73
|
-
p.vy *= opts.drag;
|
|
74
|
-
p.x += p.vx;
|
|
75
|
-
p.y += p.vy;
|
|
76
|
-
p.angle += p.spin;
|
|
77
|
-
|
|
78
|
-
drawPiece(p);
|
|
79
|
-
|
|
80
|
-
if (p.y > canvas.height + 20) pieces.splice(i, 1);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
requestAnimationFrame(animate);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
animate();
|
|
87
|
-
|
|
88
|
-
return function stop() {
|
|
89
|
-
running = false;
|
|
90
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
91
|
-
};
|
|
92
|
-
}
|
|
1
|
+
import { mergeOptions } from '../core/engine.js';
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
density: 0.18,
|
|
5
|
+
colors: ['#ff6b8a','#ff4d6d','#ffd6ff','#e7c6ff','#c77dff','#48cae4','#ffe66d','#06d6a0'],
|
|
6
|
+
minSize: 6,
|
|
7
|
+
maxSize: 14,
|
|
8
|
+
minSpeed: 1.5,
|
|
9
|
+
maxSpeed: 4,
|
|
10
|
+
gravity: 0.06,
|
|
11
|
+
drag: 0.99,
|
|
12
|
+
shapes: ['rect', 'circle', 'ribbon'],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function confetti(canvas, userOptions = {}) {
|
|
16
|
+
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
17
|
+
const ctx = canvas.getContext('2d');
|
|
18
|
+
const pieces = [];
|
|
19
|
+
let running = true;
|
|
20
|
+
|
|
21
|
+
function createPiece() {
|
|
22
|
+
const size = opts.minSize + Math.random() * (opts.maxSize - opts.minSize);
|
|
23
|
+
const speed = opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed);
|
|
24
|
+
return {
|
|
25
|
+
x: Math.random() * canvas.width,
|
|
26
|
+
y: -size * 2,
|
|
27
|
+
w: size,
|
|
28
|
+
h: size * (0.4 + Math.random() * 0.8),
|
|
29
|
+
vx: (Math.random() - 0.5) * 3,
|
|
30
|
+
vy: speed,
|
|
31
|
+
angle: Math.random() * Math.PI * 2,
|
|
32
|
+
spin: (Math.random() - 0.5) * 0.15,
|
|
33
|
+
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
34
|
+
alpha: 0.8 + Math.random() * 0.2,
|
|
35
|
+
shape: opts.shapes[Math.floor(Math.random() * opts.shapes.length)],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function drawPiece(p) {
|
|
40
|
+
ctx.save();
|
|
41
|
+
ctx.globalAlpha = p.alpha;
|
|
42
|
+
ctx.fillStyle = p.color;
|
|
43
|
+
ctx.strokeStyle = p.color;
|
|
44
|
+
ctx.translate(p.x, p.y);
|
|
45
|
+
ctx.rotate(p.angle);
|
|
46
|
+
|
|
47
|
+
if (p.shape === 'circle') {
|
|
48
|
+
ctx.beginPath();
|
|
49
|
+
ctx.ellipse(0, 0, p.w / 2, p.h / 2, 0, 0, Math.PI * 2);
|
|
50
|
+
ctx.fill();
|
|
51
|
+
} else if (p.shape === 'ribbon') {
|
|
52
|
+
ctx.beginPath();
|
|
53
|
+
ctx.moveTo(-p.w / 2, 0);
|
|
54
|
+
ctx.quadraticCurveTo(0, -p.h, p.w / 2, 0);
|
|
55
|
+
ctx.quadraticCurveTo(0, p.h, -p.w / 2, 0);
|
|
56
|
+
ctx.fill();
|
|
57
|
+
} else {
|
|
58
|
+
ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
|
|
59
|
+
}
|
|
60
|
+
ctx.restore();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function animate() {
|
|
64
|
+
if (!running) return;
|
|
65
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
66
|
+
|
|
67
|
+
if (Math.random() < opts.density) pieces.push(createPiece());
|
|
68
|
+
|
|
69
|
+
for (let i = pieces.length - 1; i >= 0; i--) {
|
|
70
|
+
const p = pieces[i];
|
|
71
|
+
p.vy += opts.gravity;
|
|
72
|
+
p.vx *= opts.drag;
|
|
73
|
+
p.vy *= opts.drag;
|
|
74
|
+
p.x += p.vx;
|
|
75
|
+
p.y += p.vy;
|
|
76
|
+
p.angle += p.spin;
|
|
77
|
+
|
|
78
|
+
drawPiece(p);
|
|
79
|
+
|
|
80
|
+
if (p.y > canvas.height + 20) pieces.splice(i, 1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
requestAnimationFrame(animate);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
animate();
|
|
87
|
+
|
|
88
|
+
return function stop() {
|
|
89
|
+
running = false;
|
|
90
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
import { mergeOptions } from '../core/engine.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULTS = {
|
|
4
|
-
interval: 1200, // ms between auto-launches
|
|
5
|
-
trailLength: 28,
|
|
6
|
-
particleCount: 80,
|
|
7
|
-
colors: ['#ff6b8a','#ff4d6d','#ffd6ff','#e7c6ff','#ffe66d','#06d6a0','#48cae4','#ffffff'],
|
|
8
|
-
gravity: 0.09,
|
|
9
|
-
decay: 0.014,
|
|
10
|
-
glow: true,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export function fireworks(canvas, userOptions = {}) {
|
|
14
|
-
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
15
|
-
const ctx = canvas.getContext('2d');
|
|
16
|
-
let running = true;
|
|
17
|
-
|
|
18
|
-
const rockets = []; // { x, y, vx, vy, trail[], color }
|
|
19
|
-
const particles = []; // burst particles
|
|
20
|
-
|
|
21
|
-
function launchRocket() {
|
|
22
|
-
const x = canvas.width * (0.2 + Math.random() * 0.6);
|
|
23
|
-
const targetY = canvas.height * (0.1 + Math.random() * 0.4);
|
|
24
|
-
const dy = targetY - canvas.height;
|
|
25
|
-
const speed = 8 + Math.random() * 5;
|
|
26
|
-
const color = opts.colors[Math.floor(Math.random() * opts.colors.length)];
|
|
27
|
-
rockets.push({ x, y: canvas.height, vy: -Math.abs(speed), targetY, trail: [], color });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function burst(x, y, color) {
|
|
31
|
-
for (let i = 0; i < opts.particleCount; i++) {
|
|
32
|
-
const angle = Math.random() * Math.PI * 2;
|
|
33
|
-
const speed = 1 + Math.random() * 5;
|
|
34
|
-
particles.push({
|
|
35
|
-
x, y,
|
|
36
|
-
vx: Math.cos(angle) * speed,
|
|
37
|
-
vy: Math.sin(angle) * speed,
|
|
38
|
-
alpha: 1,
|
|
39
|
-
decay: opts.decay * (0.7 + Math.random() * 0.6),
|
|
40
|
-
size: 2 + Math.random() * 3,
|
|
41
|
-
color,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Auto-launch interval
|
|
47
|
-
const iv = setInterval(() => { if (running) launchRocket(); }, opts.interval);
|
|
48
|
-
launchRocket(); // fire one immediately
|
|
49
|
-
|
|
50
|
-
function animate() {
|
|
51
|
-
if (!running) return;
|
|
52
|
-
|
|
53
|
-
// Clear canvas instead of using a solid fill to keep background transparent
|
|
54
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
55
|
-
|
|
56
|
-
// Draw rockets
|
|
57
|
-
for (let i = rockets.length - 1; i >= 0; i--) {
|
|
58
|
-
const r = rockets[i];
|
|
59
|
-
r.y += r.vy;
|
|
60
|
-
r.trail.push({ x: r.x, y: r.y });
|
|
61
|
-
if (r.trail.length > opts.trailLength) r.trail.shift();
|
|
62
|
-
|
|
63
|
-
// Draw trail
|
|
64
|
-
for (let t = 0; t < r.trail.length; t++) {
|
|
65
|
-
const alpha = (t / r.trail.length) * 0.8;
|
|
66
|
-
ctx.save();
|
|
67
|
-
ctx.globalAlpha = alpha;
|
|
68
|
-
if (opts.glow) { ctx.shadowColor = r.color; ctx.shadowBlur = 6; }
|
|
69
|
-
ctx.fillStyle = r.color;
|
|
70
|
-
ctx.beginPath();
|
|
71
|
-
ctx.arc(r.trail[t].x, r.trail[t].y, 2.5 * (t / r.trail.length), 0, Math.PI * 2);
|
|
72
|
-
ctx.fill();
|
|
73
|
-
ctx.restore();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (r.y <= r.targetY) {
|
|
77
|
-
burst(r.x, r.y, r.color);
|
|
78
|
-
rockets.splice(i, 1);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Draw burst particles
|
|
83
|
-
for (let i = particles.length - 1; i >= 0; i--) {
|
|
84
|
-
const p = particles[i];
|
|
85
|
-
p.x += p.vx;
|
|
86
|
-
p.y += p.vy;
|
|
87
|
-
p.vy += opts.gravity;
|
|
88
|
-
p.alpha -= p.decay;
|
|
89
|
-
|
|
90
|
-
ctx.save();
|
|
91
|
-
ctx.globalAlpha = Math.max(0, p.alpha);
|
|
92
|
-
if (opts.glow) { ctx.shadowColor = p.color; ctx.shadowBlur = p.size * 2; }
|
|
93
|
-
ctx.fillStyle = p.color;
|
|
94
|
-
ctx.beginPath();
|
|
95
|
-
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
|
96
|
-
ctx.fill();
|
|
97
|
-
ctx.restore();
|
|
98
|
-
|
|
99
|
-
if (p.alpha <= 0) particles.splice(i, 1);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
requestAnimationFrame(animate);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
animate();
|
|
106
|
-
|
|
107
|
-
return function stop() {
|
|
108
|
-
running = false;
|
|
109
|
-
clearInterval(iv);
|
|
110
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
111
|
-
};
|
|
112
|
-
}
|
|
1
|
+
import { mergeOptions } from '../core/engine.js';
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
interval: 1200, // ms between auto-launches
|
|
5
|
+
trailLength: 28,
|
|
6
|
+
particleCount: 80,
|
|
7
|
+
colors: ['#ff6b8a','#ff4d6d','#ffd6ff','#e7c6ff','#ffe66d','#06d6a0','#48cae4','#ffffff'],
|
|
8
|
+
gravity: 0.09,
|
|
9
|
+
decay: 0.014,
|
|
10
|
+
glow: true,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function fireworks(canvas, userOptions = {}) {
|
|
14
|
+
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
15
|
+
const ctx = canvas.getContext('2d');
|
|
16
|
+
let running = true;
|
|
17
|
+
|
|
18
|
+
const rockets = []; // { x, y, vx, vy, trail[], color }
|
|
19
|
+
const particles = []; // burst particles
|
|
20
|
+
|
|
21
|
+
function launchRocket() {
|
|
22
|
+
const x = canvas.width * (0.2 + Math.random() * 0.6);
|
|
23
|
+
const targetY = canvas.height * (0.1 + Math.random() * 0.4);
|
|
24
|
+
const dy = targetY - canvas.height;
|
|
25
|
+
const speed = 8 + Math.random() * 5;
|
|
26
|
+
const color = opts.colors[Math.floor(Math.random() * opts.colors.length)];
|
|
27
|
+
rockets.push({ x, y: canvas.height, vy: -Math.abs(speed), targetY, trail: [], color });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function burst(x, y, color) {
|
|
31
|
+
for (let i = 0; i < opts.particleCount; i++) {
|
|
32
|
+
const angle = Math.random() * Math.PI * 2;
|
|
33
|
+
const speed = 1 + Math.random() * 5;
|
|
34
|
+
particles.push({
|
|
35
|
+
x, y,
|
|
36
|
+
vx: Math.cos(angle) * speed,
|
|
37
|
+
vy: Math.sin(angle) * speed,
|
|
38
|
+
alpha: 1,
|
|
39
|
+
decay: opts.decay * (0.7 + Math.random() * 0.6),
|
|
40
|
+
size: 2 + Math.random() * 3,
|
|
41
|
+
color,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Auto-launch interval
|
|
47
|
+
const iv = setInterval(() => { if (running) launchRocket(); }, opts.interval);
|
|
48
|
+
launchRocket(); // fire one immediately
|
|
49
|
+
|
|
50
|
+
function animate() {
|
|
51
|
+
if (!running) return;
|
|
52
|
+
|
|
53
|
+
// Clear canvas instead of using a solid fill to keep background transparent
|
|
54
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
55
|
+
|
|
56
|
+
// Draw rockets
|
|
57
|
+
for (let i = rockets.length - 1; i >= 0; i--) {
|
|
58
|
+
const r = rockets[i];
|
|
59
|
+
r.y += r.vy;
|
|
60
|
+
r.trail.push({ x: r.x, y: r.y });
|
|
61
|
+
if (r.trail.length > opts.trailLength) r.trail.shift();
|
|
62
|
+
|
|
63
|
+
// Draw trail
|
|
64
|
+
for (let t = 0; t < r.trail.length; t++) {
|
|
65
|
+
const alpha = (t / r.trail.length) * 0.8;
|
|
66
|
+
ctx.save();
|
|
67
|
+
ctx.globalAlpha = alpha;
|
|
68
|
+
if (opts.glow) { ctx.shadowColor = r.color; ctx.shadowBlur = 6; }
|
|
69
|
+
ctx.fillStyle = r.color;
|
|
70
|
+
ctx.beginPath();
|
|
71
|
+
ctx.arc(r.trail[t].x, r.trail[t].y, 2.5 * (t / r.trail.length), 0, Math.PI * 2);
|
|
72
|
+
ctx.fill();
|
|
73
|
+
ctx.restore();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (r.y <= r.targetY) {
|
|
77
|
+
burst(r.x, r.y, r.color);
|
|
78
|
+
rockets.splice(i, 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Draw burst particles
|
|
83
|
+
for (let i = particles.length - 1; i >= 0; i--) {
|
|
84
|
+
const p = particles[i];
|
|
85
|
+
p.x += p.vx;
|
|
86
|
+
p.y += p.vy;
|
|
87
|
+
p.vy += opts.gravity;
|
|
88
|
+
p.alpha -= p.decay;
|
|
89
|
+
|
|
90
|
+
ctx.save();
|
|
91
|
+
ctx.globalAlpha = Math.max(0, p.alpha);
|
|
92
|
+
if (opts.glow) { ctx.shadowColor = p.color; ctx.shadowBlur = p.size * 2; }
|
|
93
|
+
ctx.fillStyle = p.color;
|
|
94
|
+
ctx.beginPath();
|
|
95
|
+
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
|
|
96
|
+
ctx.fill();
|
|
97
|
+
ctx.restore();
|
|
98
|
+
|
|
99
|
+
if (p.alpha <= 0) particles.splice(i, 1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
requestAnimationFrame(animate);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
animate();
|
|
106
|
+
|
|
107
|
+
return function stop() {
|
|
108
|
+
running = false;
|
|
109
|
+
clearInterval(iv);
|
|
110
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
111
|
+
};
|
|
112
|
+
}
|