@sarthak03dot/romantic-animations 1.2.0 → 1.2.3
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 +332 -327
- package/dist/romantic-animations.es.js.map +1 -1
- package/dist/romantic-animations.umd.js.map +1 -1
- package/package.json +54 -54
- 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,85 +1,85 @@
|
|
|
1
|
-
import { mergeOptions } from '../core/engine.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULTS = {
|
|
4
|
-
minSize: 6,
|
|
5
|
-
maxSize: 16,
|
|
6
|
-
decay: 0.025,
|
|
7
|
-
colors: ['#ff6b8a', '#ff4d6d', '#ff85a1', '#ffc2d1', '#c9184a'],
|
|
8
|
-
glow: true,
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
function drawHeart(ctx, cx, cy, r, color, alpha, glow) {
|
|
12
|
-
ctx.save();
|
|
13
|
-
ctx.globalAlpha = Math.max(0, alpha);
|
|
14
|
-
if (glow) {
|
|
15
|
-
ctx.shadowColor = color;
|
|
16
|
-
ctx.shadowBlur = r * 2;
|
|
17
|
-
}
|
|
18
|
-
ctx.fillStyle = color;
|
|
19
|
-
ctx.beginPath();
|
|
20
|
-
ctx.moveTo(cx, cy + r * 0.3);
|
|
21
|
-
ctx.bezierCurveTo(cx - r * 1.1, cy - r * 0.5, cx - r * 1.6, cy + r * 0.5, cx, cy + r * 1.4);
|
|
22
|
-
ctx.bezierCurveTo(cx + r * 1.6, cy + r * 0.5, cx + r * 1.1, cy - r * 0.5, cx, cy + r * 0.3);
|
|
23
|
-
ctx.fill();
|
|
24
|
-
ctx.restore();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function heartTrail(canvas, userOptions = {}) {
|
|
28
|
-
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
29
|
-
const ctx = canvas.getContext('2d');
|
|
30
|
-
const hearts = [];
|
|
31
|
-
let running = true;
|
|
32
|
-
|
|
33
|
-
function addHeart(x, y) {
|
|
34
|
-
hearts.push({
|
|
35
|
-
x,
|
|
36
|
-
y,
|
|
37
|
-
size: opts.minSize + Math.random() * (opts.maxSize - opts.minSize),
|
|
38
|
-
alpha: 0.9 + Math.random() * 0.1,
|
|
39
|
-
decay: opts.decay * (0.8 + Math.random() * 0.4),
|
|
40
|
-
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
41
|
-
vy: -(0.3 + Math.random() * 0.6), // drift upward
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Mouse support
|
|
46
|
-
const onMouseMove = (e) => {
|
|
47
|
-
const rect = canvas.getBoundingClientRect();
|
|
48
|
-
addHeart(e.clientX - rect.left, e.clientY - rect.top);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// Touch support
|
|
52
|
-
const onTouchMove = (e) => {
|
|
53
|
-
const rect = canvas.getBoundingClientRect();
|
|
54
|
-
Array.from(e.touches).forEach((t) => {
|
|
55
|
-
addHeart(t.clientX - rect.left, t.clientY - rect.top);
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
window.addEventListener('mousemove', onMouseMove);
|
|
60
|
-
window.addEventListener('touchmove', onTouchMove, { passive: true });
|
|
61
|
-
|
|
62
|
-
function animate() {
|
|
63
|
-
if (!running) return;
|
|
64
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
65
|
-
|
|
66
|
-
for (let i = hearts.length - 1; i >= 0; i--) {
|
|
67
|
-
const h = hearts[i];
|
|
68
|
-
h.y += h.vy;
|
|
69
|
-
drawHeart(ctx, h.x, h.y, h.size, h.color, h.alpha, opts.glow);
|
|
70
|
-
h.alpha -= h.decay;
|
|
71
|
-
if (h.alpha <= 0) hearts.splice(i, 1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
requestAnimationFrame(animate);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
animate();
|
|
78
|
-
|
|
79
|
-
return function stop() {
|
|
80
|
-
running = false;
|
|
81
|
-
window.removeEventListener('mousemove', onMouseMove);
|
|
82
|
-
window.removeEventListener('touchmove', onTouchMove);
|
|
83
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
84
|
-
};
|
|
85
|
-
}
|
|
1
|
+
import { mergeOptions } from '../core/engine.js';
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
minSize: 6,
|
|
5
|
+
maxSize: 16,
|
|
6
|
+
decay: 0.025,
|
|
7
|
+
colors: ['#ff6b8a', '#ff4d6d', '#ff85a1', '#ffc2d1', '#c9184a'],
|
|
8
|
+
glow: true,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function drawHeart(ctx, cx, cy, r, color, alpha, glow) {
|
|
12
|
+
ctx.save();
|
|
13
|
+
ctx.globalAlpha = Math.max(0, alpha);
|
|
14
|
+
if (glow) {
|
|
15
|
+
ctx.shadowColor = color;
|
|
16
|
+
ctx.shadowBlur = r * 2;
|
|
17
|
+
}
|
|
18
|
+
ctx.fillStyle = color;
|
|
19
|
+
ctx.beginPath();
|
|
20
|
+
ctx.moveTo(cx, cy + r * 0.3);
|
|
21
|
+
ctx.bezierCurveTo(cx - r * 1.1, cy - r * 0.5, cx - r * 1.6, cy + r * 0.5, cx, cy + r * 1.4);
|
|
22
|
+
ctx.bezierCurveTo(cx + r * 1.6, cy + r * 0.5, cx + r * 1.1, cy - r * 0.5, cx, cy + r * 0.3);
|
|
23
|
+
ctx.fill();
|
|
24
|
+
ctx.restore();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function heartTrail(canvas, userOptions = {}) {
|
|
28
|
+
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
29
|
+
const ctx = canvas.getContext('2d');
|
|
30
|
+
const hearts = [];
|
|
31
|
+
let running = true;
|
|
32
|
+
|
|
33
|
+
function addHeart(x, y) {
|
|
34
|
+
hearts.push({
|
|
35
|
+
x,
|
|
36
|
+
y,
|
|
37
|
+
size: opts.minSize + Math.random() * (opts.maxSize - opts.minSize),
|
|
38
|
+
alpha: 0.9 + Math.random() * 0.1,
|
|
39
|
+
decay: opts.decay * (0.8 + Math.random() * 0.4),
|
|
40
|
+
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
41
|
+
vy: -(0.3 + Math.random() * 0.6), // drift upward
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Mouse support
|
|
46
|
+
const onMouseMove = (e) => {
|
|
47
|
+
const rect = canvas.getBoundingClientRect();
|
|
48
|
+
addHeart(e.clientX - rect.left, e.clientY - rect.top);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Touch support
|
|
52
|
+
const onTouchMove = (e) => {
|
|
53
|
+
const rect = canvas.getBoundingClientRect();
|
|
54
|
+
Array.from(e.touches).forEach((t) => {
|
|
55
|
+
addHeart(t.clientX - rect.left, t.clientY - rect.top);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
60
|
+
window.addEventListener('touchmove', onTouchMove, { passive: true });
|
|
61
|
+
|
|
62
|
+
function animate() {
|
|
63
|
+
if (!running) return;
|
|
64
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
65
|
+
|
|
66
|
+
for (let i = hearts.length - 1; i >= 0; i--) {
|
|
67
|
+
const h = hearts[i];
|
|
68
|
+
h.y += h.vy;
|
|
69
|
+
drawHeart(ctx, h.x, h.y, h.size, h.color, h.alpha, opts.glow);
|
|
70
|
+
h.alpha -= h.decay;
|
|
71
|
+
if (h.alpha <= 0) hearts.splice(i, 1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
requestAnimationFrame(animate);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
animate();
|
|
78
|
+
|
|
79
|
+
return function stop() {
|
|
80
|
+
running = false;
|
|
81
|
+
window.removeEventListener('mousemove', onMouseMove);
|
|
82
|
+
window.removeEventListener('touchmove', onTouchMove);
|
|
83
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
import { mergeOptions } from '../core/engine.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULTS = {
|
|
4
|
-
density: 0.15, // probability of a new drop per frame
|
|
5
|
-
symbols: ['❤', '💕', '✨', '💖', '💗', '⭐', '×'],
|
|
6
|
-
minSize: 12,
|
|
7
|
-
maxSize: 28,
|
|
8
|
-
minSpeed: 1,
|
|
9
|
-
maxSpeed: 3.5,
|
|
10
|
-
colors: ['#ff6b8a', '#ff4d6d', '#ffc2d1', '#ff85a1', '#ff0a54', '#a2d2ff'],
|
|
11
|
-
opacity: 0.85,
|
|
12
|
-
glow: true,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function loveRain(canvas, userOptions = {}) {
|
|
16
|
-
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
17
|
-
const ctx = canvas.getContext('2d');
|
|
18
|
-
const drops = [];
|
|
19
|
-
let running = true;
|
|
20
|
-
|
|
21
|
-
function createDrop() {
|
|
22
|
-
const size = opts.minSize + Math.random() * (opts.maxSize - opts.minSize);
|
|
23
|
-
return {
|
|
24
|
-
x: Math.random() * canvas.width,
|
|
25
|
-
y: -size * 2,
|
|
26
|
-
size,
|
|
27
|
-
speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
|
|
28
|
-
symbol: opts.symbols[Math.floor(Math.random() * opts.symbols.length)],
|
|
29
|
-
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
30
|
-
alpha: 0.4 + Math.random() * 0.6,
|
|
31
|
-
angle: (Math.random() - 0.5) * 0.4, // slight tilt
|
|
32
|
-
wobble: Math.random() * Math.PI * 2,
|
|
33
|
-
wobbleSpeed: 0.02 + Math.random() * 0.02,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function animate() {
|
|
38
|
-
if (!running) return;
|
|
39
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
40
|
-
|
|
41
|
-
if (Math.random() < opts.density) drops.push(createDrop());
|
|
42
|
-
|
|
43
|
-
for (let i = drops.length - 1; i >= 0; i--) {
|
|
44
|
-
const d = drops[i];
|
|
45
|
-
d.y += d.speed;
|
|
46
|
-
d.wobble += d.wobbleSpeed;
|
|
47
|
-
const xOff = Math.sin(d.wobble) * d.size * 0.3;
|
|
48
|
-
|
|
49
|
-
ctx.save();
|
|
50
|
-
ctx.globalAlpha = d.alpha * opts.opacity;
|
|
51
|
-
ctx.font = `${d.size}px serif`;
|
|
52
|
-
ctx.fillStyle = d.color;
|
|
53
|
-
if (opts.glow) { ctx.shadowColor = d.color; ctx.shadowBlur = d.size * 0.8; }
|
|
54
|
-
ctx.translate(d.x + xOff, d.y);
|
|
55
|
-
ctx.rotate(d.angle);
|
|
56
|
-
ctx.fillText(d.symbol, 0, 0);
|
|
57
|
-
ctx.restore();
|
|
58
|
-
|
|
59
|
-
if (d.y > canvas.height + d.size * 2) drops.splice(i, 1);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
requestAnimationFrame(animate);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
animate();
|
|
66
|
-
|
|
67
|
-
return function stop() {
|
|
68
|
-
running = false;
|
|
69
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
70
|
-
};
|
|
71
|
-
}
|
|
1
|
+
import { mergeOptions } from '../core/engine.js';
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
density: 0.15, // probability of a new drop per frame
|
|
5
|
+
symbols: ['❤', '💕', '✨', '💖', '💗', '⭐', '×'],
|
|
6
|
+
minSize: 12,
|
|
7
|
+
maxSize: 28,
|
|
8
|
+
minSpeed: 1,
|
|
9
|
+
maxSpeed: 3.5,
|
|
10
|
+
colors: ['#ff6b8a', '#ff4d6d', '#ffc2d1', '#ff85a1', '#ff0a54', '#a2d2ff'],
|
|
11
|
+
opacity: 0.85,
|
|
12
|
+
glow: true,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function loveRain(canvas, userOptions = {}) {
|
|
16
|
+
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
17
|
+
const ctx = canvas.getContext('2d');
|
|
18
|
+
const drops = [];
|
|
19
|
+
let running = true;
|
|
20
|
+
|
|
21
|
+
function createDrop() {
|
|
22
|
+
const size = opts.minSize + Math.random() * (opts.maxSize - opts.minSize);
|
|
23
|
+
return {
|
|
24
|
+
x: Math.random() * canvas.width,
|
|
25
|
+
y: -size * 2,
|
|
26
|
+
size,
|
|
27
|
+
speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
|
|
28
|
+
symbol: opts.symbols[Math.floor(Math.random() * opts.symbols.length)],
|
|
29
|
+
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
30
|
+
alpha: 0.4 + Math.random() * 0.6,
|
|
31
|
+
angle: (Math.random() - 0.5) * 0.4, // slight tilt
|
|
32
|
+
wobble: Math.random() * Math.PI * 2,
|
|
33
|
+
wobbleSpeed: 0.02 + Math.random() * 0.02,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function animate() {
|
|
38
|
+
if (!running) return;
|
|
39
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
40
|
+
|
|
41
|
+
if (Math.random() < opts.density) drops.push(createDrop());
|
|
42
|
+
|
|
43
|
+
for (let i = drops.length - 1; i >= 0; i--) {
|
|
44
|
+
const d = drops[i];
|
|
45
|
+
d.y += d.speed;
|
|
46
|
+
d.wobble += d.wobbleSpeed;
|
|
47
|
+
const xOff = Math.sin(d.wobble) * d.size * 0.3;
|
|
48
|
+
|
|
49
|
+
ctx.save();
|
|
50
|
+
ctx.globalAlpha = d.alpha * opts.opacity;
|
|
51
|
+
ctx.font = `${d.size}px serif`;
|
|
52
|
+
ctx.fillStyle = d.color;
|
|
53
|
+
if (opts.glow) { ctx.shadowColor = d.color; ctx.shadowBlur = d.size * 0.8; }
|
|
54
|
+
ctx.translate(d.x + xOff, d.y);
|
|
55
|
+
ctx.rotate(d.angle);
|
|
56
|
+
ctx.fillText(d.symbol, 0, 0);
|
|
57
|
+
ctx.restore();
|
|
58
|
+
|
|
59
|
+
if (d.y > canvas.height + d.size * 2) drops.splice(i, 1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
requestAnimationFrame(animate);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
animate();
|
|
66
|
+
|
|
67
|
+
return function stop() {
|
|
68
|
+
running = false;
|
|
69
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import { mergeOptions } from '../core/engine.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULTS = {
|
|
4
|
-
particleCount: 150,
|
|
5
|
-
minSize: 1,
|
|
6
|
-
maxSize: 4,
|
|
7
|
-
colors: ['#ffd6ff', '#e7c6ff', '#c77dff', '#ffb3c1', '#ffffff'],
|
|
8
|
-
speed: 0.8,
|
|
9
|
-
glow: true,
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export function magicDust(canvas, userOptions = {}) {
|
|
13
|
-
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
14
|
-
const ctx = canvas.getContext('2d');
|
|
15
|
-
const dusts = [];
|
|
16
|
-
let running = true;
|
|
17
|
-
let time = 0;
|
|
18
|
-
|
|
19
|
-
function createDust() {
|
|
20
|
-
return {
|
|
21
|
-
x: Math.random() * canvas.width,
|
|
22
|
-
y: Math.random() * canvas.height,
|
|
23
|
-
size: opts.minSize + Math.random() * (opts.maxSize - opts.minSize),
|
|
24
|
-
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
25
|
-
angle: Math.random() * Math.PI * 2,
|
|
26
|
-
orbitRadius: 20 + Math.random() * 80,
|
|
27
|
-
orbitSpeed: (0.01 + Math.random() * 0.03) * (Math.random() > 0.5 ? 1 : -1),
|
|
28
|
-
centerX: Math.random() * canvas.width,
|
|
29
|
-
centerY: canvas.height + 50, // Start below and move up
|
|
30
|
-
upwardSpeed: opts.speed + Math.random() * 1.5,
|
|
31
|
-
alpha: 0,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
for (let i = 0; i < opts.particleCount; i++) {
|
|
36
|
-
dusts.push(createDust());
|
|
37
|
-
// Scatter initial positions so they aren't all at the bottom
|
|
38
|
-
dusts[i].centerY = Math.random() * canvas.height;
|
|
39
|
-
dusts[i].alpha = Math.random();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function animate() {
|
|
43
|
-
if (!running) return;
|
|
44
|
-
time++;
|
|
45
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
46
|
-
|
|
47
|
-
for (let i = 0; i < dusts.length; i++) {
|
|
48
|
-
const d = dusts[i];
|
|
49
|
-
d.angle += d.orbitSpeed;
|
|
50
|
-
d.centerY -= d.upwardSpeed;
|
|
51
|
-
|
|
52
|
-
// Swirling calculation
|
|
53
|
-
d.x = d.centerX + Math.cos(d.angle) * d.orbitRadius + Math.sin(time * 0.01 + d.angle) * 30;
|
|
54
|
-
d.y = d.centerY + Math.sin(d.angle) * (d.orbitRadius * 0.5);
|
|
55
|
-
|
|
56
|
-
if (d.alpha < 1 && d.centerY > 0) d.alpha += 0.01;
|
|
57
|
-
|
|
58
|
-
// Reset when they reach top
|
|
59
|
-
if (d.y < -50) {
|
|
60
|
-
Object.assign(d, createDust());
|
|
61
|
-
d.centerY = canvas.height + 50;
|
|
62
|
-
d.centerX = Math.random() * canvas.width;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
ctx.save();
|
|
66
|
-
ctx.globalAlpha = d.alpha;
|
|
67
|
-
if (opts.glow) {
|
|
68
|
-
ctx.shadowColor = d.color;
|
|
69
|
-
ctx.shadowBlur = d.size * 3;
|
|
70
|
-
}
|
|
71
|
-
ctx.fillStyle = d.color;
|
|
72
|
-
ctx.beginPath();
|
|
73
|
-
ctx.arc(d.x, d.y, d.size, 0, Math.PI * 2);
|
|
74
|
-
ctx.fill();
|
|
75
|
-
ctx.restore();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
requestAnimationFrame(animate);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
animate();
|
|
82
|
-
|
|
83
|
-
return function stop() {
|
|
84
|
-
running = false;
|
|
85
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
86
|
-
};
|
|
87
|
-
}
|
|
1
|
+
import { mergeOptions } from '../core/engine.js';
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
particleCount: 150,
|
|
5
|
+
minSize: 1,
|
|
6
|
+
maxSize: 4,
|
|
7
|
+
colors: ['#ffd6ff', '#e7c6ff', '#c77dff', '#ffb3c1', '#ffffff'],
|
|
8
|
+
speed: 0.8,
|
|
9
|
+
glow: true,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function magicDust(canvas, userOptions = {}) {
|
|
13
|
+
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
14
|
+
const ctx = canvas.getContext('2d');
|
|
15
|
+
const dusts = [];
|
|
16
|
+
let running = true;
|
|
17
|
+
let time = 0;
|
|
18
|
+
|
|
19
|
+
function createDust() {
|
|
20
|
+
return {
|
|
21
|
+
x: Math.random() * canvas.width,
|
|
22
|
+
y: Math.random() * canvas.height,
|
|
23
|
+
size: opts.minSize + Math.random() * (opts.maxSize - opts.minSize),
|
|
24
|
+
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
25
|
+
angle: Math.random() * Math.PI * 2,
|
|
26
|
+
orbitRadius: 20 + Math.random() * 80,
|
|
27
|
+
orbitSpeed: (0.01 + Math.random() * 0.03) * (Math.random() > 0.5 ? 1 : -1),
|
|
28
|
+
centerX: Math.random() * canvas.width,
|
|
29
|
+
centerY: canvas.height + 50, // Start below and move up
|
|
30
|
+
upwardSpeed: opts.speed + Math.random() * 1.5,
|
|
31
|
+
alpha: 0,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < opts.particleCount; i++) {
|
|
36
|
+
dusts.push(createDust());
|
|
37
|
+
// Scatter initial positions so they aren't all at the bottom
|
|
38
|
+
dusts[i].centerY = Math.random() * canvas.height;
|
|
39
|
+
dusts[i].alpha = Math.random();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function animate() {
|
|
43
|
+
if (!running) return;
|
|
44
|
+
time++;
|
|
45
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < dusts.length; i++) {
|
|
48
|
+
const d = dusts[i];
|
|
49
|
+
d.angle += d.orbitSpeed;
|
|
50
|
+
d.centerY -= d.upwardSpeed;
|
|
51
|
+
|
|
52
|
+
// Swirling calculation
|
|
53
|
+
d.x = d.centerX + Math.cos(d.angle) * d.orbitRadius + Math.sin(time * 0.01 + d.angle) * 30;
|
|
54
|
+
d.y = d.centerY + Math.sin(d.angle) * (d.orbitRadius * 0.5);
|
|
55
|
+
|
|
56
|
+
if (d.alpha < 1 && d.centerY > 0) d.alpha += 0.01;
|
|
57
|
+
|
|
58
|
+
// Reset when they reach top
|
|
59
|
+
if (d.y < -50) {
|
|
60
|
+
Object.assign(d, createDust());
|
|
61
|
+
d.centerY = canvas.height + 50;
|
|
62
|
+
d.centerX = Math.random() * canvas.width;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
ctx.save();
|
|
66
|
+
ctx.globalAlpha = d.alpha;
|
|
67
|
+
if (opts.glow) {
|
|
68
|
+
ctx.shadowColor = d.color;
|
|
69
|
+
ctx.shadowBlur = d.size * 3;
|
|
70
|
+
}
|
|
71
|
+
ctx.fillStyle = d.color;
|
|
72
|
+
ctx.beginPath();
|
|
73
|
+
ctx.arc(d.x, d.y, d.size, 0, Math.PI * 2);
|
|
74
|
+
ctx.fill();
|
|
75
|
+
ctx.restore();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
requestAnimationFrame(animate);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
animate();
|
|
82
|
+
|
|
83
|
+
return function stop() {
|
|
84
|
+
running = false;
|
|
85
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
import { mergeOptions } from '../core/engine.js';
|
|
2
|
-
|
|
3
|
-
const DEFAULTS = {
|
|
4
|
-
density: 0.02,
|
|
5
|
-
minSpeed: 10,
|
|
6
|
-
maxSpeed: 25,
|
|
7
|
-
colors: ['#ffffff', '#e7c6ff', '#48cae4', '#ffe66d'],
|
|
8
|
-
glow: true,
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export function shootingStars(canvas, userOptions = {}) {
|
|
12
|
-
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
13
|
-
const ctx = canvas.getContext('2d');
|
|
14
|
-
const stars = [];
|
|
15
|
-
let running = true;
|
|
16
|
-
|
|
17
|
-
function createStar() {
|
|
18
|
-
return {
|
|
19
|
-
x: Math.random() * canvas.width * 1.5,
|
|
20
|
-
y: -50,
|
|
21
|
-
length: 50 + Math.random() * 100,
|
|
22
|
-
thickness: 1 + Math.random() * 2,
|
|
23
|
-
speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
|
|
24
|
-
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
25
|
-
angle: (Math.PI / 4) + (Math.random() * 0.2 - 0.1), // Roughly 45 degrees
|
|
26
|
-
opacity: 1,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function animate() {
|
|
31
|
-
if (!running) return;
|
|
32
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
33
|
-
|
|
34
|
-
if (Math.random() < opts.density) {
|
|
35
|
-
stars.push(createStar());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
for (let i = stars.length - 1; i >= 0; i--) {
|
|
39
|
-
const s = stars[i];
|
|
40
|
-
const vx = -Math.cos(s.angle) * s.speed;
|
|
41
|
-
const vy = Math.sin(s.angle) * s.speed;
|
|
42
|
-
|
|
43
|
-
s.x += vx;
|
|
44
|
-
s.y += vy;
|
|
45
|
-
s.opacity -= 0.01;
|
|
46
|
-
|
|
47
|
-
ctx.save();
|
|
48
|
-
ctx.globalAlpha = Math.max(0, s.opacity);
|
|
49
|
-
if (opts.glow) {
|
|
50
|
-
ctx.shadowColor = s.color;
|
|
51
|
-
ctx.shadowBlur = s.thickness * 4;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const grad = ctx.createLinearGradient(s.x, s.y, s.x - vx * (s.length / s.speed), s.y - vy * (s.length / s.speed));
|
|
55
|
-
grad.addColorStop(0, s.color);
|
|
56
|
-
grad.addColorStop(1, 'rgba(255,255,255,0)');
|
|
57
|
-
|
|
58
|
-
ctx.strokeStyle = grad;
|
|
59
|
-
ctx.lineWidth = s.thickness;
|
|
60
|
-
ctx.lineCap = 'round';
|
|
61
|
-
|
|
62
|
-
ctx.beginPath();
|
|
63
|
-
ctx.moveTo(s.x, s.y);
|
|
64
|
-
ctx.lineTo(s.x - vx * (s.length / s.speed), s.y - vy * (s.length / s.speed));
|
|
65
|
-
ctx.stroke();
|
|
66
|
-
ctx.restore();
|
|
67
|
-
|
|
68
|
-
if (s.opacity <= 0 || s.x < -100 || s.y > canvas.height + 100) {
|
|
69
|
-
stars.splice(i, 1);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
requestAnimationFrame(animate);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
animate();
|
|
77
|
-
|
|
78
|
-
return function stop() {
|
|
79
|
-
running = false;
|
|
80
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
81
|
-
};
|
|
82
|
-
}
|
|
1
|
+
import { mergeOptions } from '../core/engine.js';
|
|
2
|
+
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
density: 0.02,
|
|
5
|
+
minSpeed: 10,
|
|
6
|
+
maxSpeed: 25,
|
|
7
|
+
colors: ['#ffffff', '#e7c6ff', '#48cae4', '#ffe66d'],
|
|
8
|
+
glow: true,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function shootingStars(canvas, userOptions = {}) {
|
|
12
|
+
const opts = mergeOptions(DEFAULTS, userOptions);
|
|
13
|
+
const ctx = canvas.getContext('2d');
|
|
14
|
+
const stars = [];
|
|
15
|
+
let running = true;
|
|
16
|
+
|
|
17
|
+
function createStar() {
|
|
18
|
+
return {
|
|
19
|
+
x: Math.random() * canvas.width * 1.5,
|
|
20
|
+
y: -50,
|
|
21
|
+
length: 50 + Math.random() * 100,
|
|
22
|
+
thickness: 1 + Math.random() * 2,
|
|
23
|
+
speed: opts.minSpeed + Math.random() * (opts.maxSpeed - opts.minSpeed),
|
|
24
|
+
color: opts.colors[Math.floor(Math.random() * opts.colors.length)],
|
|
25
|
+
angle: (Math.PI / 4) + (Math.random() * 0.2 - 0.1), // Roughly 45 degrees
|
|
26
|
+
opacity: 1,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function animate() {
|
|
31
|
+
if (!running) return;
|
|
32
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
33
|
+
|
|
34
|
+
if (Math.random() < opts.density) {
|
|
35
|
+
stars.push(createStar());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (let i = stars.length - 1; i >= 0; i--) {
|
|
39
|
+
const s = stars[i];
|
|
40
|
+
const vx = -Math.cos(s.angle) * s.speed;
|
|
41
|
+
const vy = Math.sin(s.angle) * s.speed;
|
|
42
|
+
|
|
43
|
+
s.x += vx;
|
|
44
|
+
s.y += vy;
|
|
45
|
+
s.opacity -= 0.01;
|
|
46
|
+
|
|
47
|
+
ctx.save();
|
|
48
|
+
ctx.globalAlpha = Math.max(0, s.opacity);
|
|
49
|
+
if (opts.glow) {
|
|
50
|
+
ctx.shadowColor = s.color;
|
|
51
|
+
ctx.shadowBlur = s.thickness * 4;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const grad = ctx.createLinearGradient(s.x, s.y, s.x - vx * (s.length / s.speed), s.y - vy * (s.length / s.speed));
|
|
55
|
+
grad.addColorStop(0, s.color);
|
|
56
|
+
grad.addColorStop(1, 'rgba(255,255,255,0)');
|
|
57
|
+
|
|
58
|
+
ctx.strokeStyle = grad;
|
|
59
|
+
ctx.lineWidth = s.thickness;
|
|
60
|
+
ctx.lineCap = 'round';
|
|
61
|
+
|
|
62
|
+
ctx.beginPath();
|
|
63
|
+
ctx.moveTo(s.x, s.y);
|
|
64
|
+
ctx.lineTo(s.x - vx * (s.length / s.speed), s.y - vy * (s.length / s.speed));
|
|
65
|
+
ctx.stroke();
|
|
66
|
+
ctx.restore();
|
|
67
|
+
|
|
68
|
+
if (s.opacity <= 0 || s.x < -100 || s.y > canvas.height + 100) {
|
|
69
|
+
stars.splice(i, 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
requestAnimationFrame(animate);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
animate();
|
|
77
|
+
|
|
78
|
+
return function stop() {
|
|
79
|
+
running = false;
|
|
80
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
81
|
+
};
|
|
82
|
+
}
|