@mar7th/firework 1.0.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/404.html +25 -0
- package/LICENSE +21 -0
- package/advanced-guide.html +135 -0
- package/api-download.html +164 -0
- package/demo.html +83 -0
- package/dist-lib/spark-fireworks.js +875 -0
- package/dist-lib/spark-fireworks.umd.cjs +1 -0
- package/docs/api-guide.md +750 -0
- package/docs/performance-optimization-plan.md +396 -0
- package/examples/production.html +203 -0
- package/favicon.jpg +0 -0
- package/guide.html +89 -0
- package/index.html +60 -0
- package/package.json +38 -0
- package/script/script1.js +34 -0
- package/script/script2.js +38 -0
- package/script/script3.js +63 -0
- package/script/script4.js +51 -0
- package/script/script5.js +55 -0
- package/scripts/copy-dist-lib.mjs +4 -0
- package/src/app.js +49 -0
- package/src/config/defaults.js +302 -0
- package/src/core/Firework.js +213 -0
- package/src/core/FireworksSimulator.js +439 -0
- package/src/core/Particle.js +174 -0
- package/src/core/ParticleSystem.js +56 -0
- package/src/core/Renderer.js +44 -0
- package/src/demo.js +298 -0
- package/src/effects/BackgroundManager.js +100 -0
- package/src/pages.css +388 -0
- package/src/pages.js +1 -0
- package/src/plugin.js +9 -0
- package/src/styles.css +544 -0
- package/src/ui/ControlPanel.js +205 -0
- package/src/ui/PerformancePanel.js +23 -0
- package/src/ui/PresetManager.js +128 -0
- package/src/utils/color.js +54 -0
- package/src/utils/eventBus.js +21 -0
- package/src/utils/math.js +29 -0
- package/vite.config.js +17 -0
- package/vite.lib.config.js +19 -0
package/index.html
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/jpeg" href="/favicon.jpg" />
|
|
7
|
+
<title>宵宫的烟花工厂</title>
|
|
8
|
+
<script>
|
|
9
|
+
var _hmt = _hmt || [];
|
|
10
|
+
(function() {
|
|
11
|
+
var hm = document.createElement("script");
|
|
12
|
+
hm.src = "https://hm.baidu.com/hm.js?c39438c75acf24e0e70a36e03a29e613";
|
|
13
|
+
var s = document.getElementsByTagName("script")[0];
|
|
14
|
+
s.parentNode.insertBefore(hm, s);
|
|
15
|
+
})();
|
|
16
|
+
</script>
|
|
17
|
+
<script type="module" src="/src/pages.js"></script>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<main class="site-shell home-shell">
|
|
21
|
+
<nav class="site-nav" aria-label="站点导航">
|
|
22
|
+
<a class="brand" href="/">宵宫的烟花工厂</a>
|
|
23
|
+
<div class="nav-links">
|
|
24
|
+
<a href="/demo.html">功能演示</a>
|
|
25
|
+
<a href="/guide.html">使用指导</a>
|
|
26
|
+
<a href="/advanced-guide.html">高级指导</a>
|
|
27
|
+
<a href="/api-download.html">API 下载</a>
|
|
28
|
+
</div>
|
|
29
|
+
</nav>
|
|
30
|
+
|
|
31
|
+
<section class="hero home-hero">
|
|
32
|
+
<div class="hero-preview" aria-label="功能演示预览">
|
|
33
|
+
<iframe src="/demo.html?embed=1" title="SparkFireworks 演示预览"></iframe>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="hero-content">
|
|
37
|
+
<h1>宵宫的烟花工厂</h1>
|
|
38
|
+
<p>
|
|
39
|
+
这里是为宵宫准备的网页烟花工坊:调配颜色、选择形状、编写脚本,把夏祭夜空里的每一次绽放都做成专属配方。
|
|
40
|
+
从功能演示开始制作一束烟花,也可以阅读指南,把这座小小工厂带到自己的页面里。
|
|
41
|
+
</p>
|
|
42
|
+
<section class="yoimiya-note" aria-label="宵宫的小纸条">
|
|
43
|
+
<h2>宵宫的小纸条</h2>
|
|
44
|
+
<p>
|
|
45
|
+
宵宫交给你的用来记录烟花配方的纸条,听说只要将这张纸条交给宵宫,她就能做出和你记忆中完全一致的烟花。
|
|
46
|
+
</p>
|
|
47
|
+
<p>
|
|
48
|
+
哪怕时光荏苒,物是人非,只要好好保管手里的这张纸条,就握住了通往过去的门票。希望在漫天烟火之下,你也能想起专属于你的美好。
|
|
49
|
+
</p>
|
|
50
|
+
</section>
|
|
51
|
+
<div class="hero-actions">
|
|
52
|
+
<a class="button-link primary" href="/demo.html">打开功能演示</a>
|
|
53
|
+
<a class="button-link" href="/guide.html">阅读使用指导</a>
|
|
54
|
+
<a class="button-link" href="/api-download.html">下载构建产物</a>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</section>
|
|
58
|
+
</main>
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mar7th/firework",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist-lib/spark-fireworks.umd.cjs",
|
|
6
|
+
"module": "./dist-lib/spark-fireworks.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist-lib/spark-fireworks.js",
|
|
10
|
+
"require": "./dist-lib/spark-fireworks.umd.cjs"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist-lib",
|
|
15
|
+
"src",
|
|
16
|
+
"script",
|
|
17
|
+
"scripts",
|
|
18
|
+
"docs",
|
|
19
|
+
"examples",
|
|
20
|
+
"*.html",
|
|
21
|
+
"favicon.jpg",
|
|
22
|
+
"vite.config.js",
|
|
23
|
+
"vite.lib.config.js"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "vite --host 127.0.0.1",
|
|
30
|
+
"build": "npm run build:lib && vite build && node scripts/copy-dist-lib.mjs",
|
|
31
|
+
"build:lib": "vite build --config vite.lib.config.js",
|
|
32
|
+
"prepack": "npm run build:lib",
|
|
33
|
+
"preview": "vite preview --host 127.0.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"vite": "^5.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function createParticles(canvasData) {
|
|
2
|
+
const particles = [];
|
|
3
|
+
const count = 360;
|
|
4
|
+
const maxRadius = 120; // 星尖最大半径
|
|
5
|
+
const minRadius = 45; // 星凹最小半径
|
|
6
|
+
const speedScale = 0.12; // 速度缩放系数
|
|
7
|
+
|
|
8
|
+
for (let i = 0; i < count; i += 1) {
|
|
9
|
+
const angle = (i / count) * Math.PI * 2;
|
|
10
|
+
|
|
11
|
+
// 五角星半径公式:每72度(2PI/5)出现一个尖角
|
|
12
|
+
// 0.4 + 0.6 * |cos(2.5*angle)|^0.4 产生尖锐的星形起伏
|
|
13
|
+
const petalFactor = 0.4 + 0.6 * Math.pow(Math.abs(Math.cos(2.5 * angle)), 0.4);
|
|
14
|
+
const radius = minRadius + (maxRadius - minRadius) * petalFactor;
|
|
15
|
+
|
|
16
|
+
// 速度方向指向星形轮廓,大小与半径成正比
|
|
17
|
+
const speed = radius * speedScale;
|
|
18
|
+
|
|
19
|
+
particles.push({
|
|
20
|
+
x: canvasData.x,
|
|
21
|
+
y: canvasData.y,
|
|
22
|
+
vx: Math.cos(angle) * speed,
|
|
23
|
+
vy: Math.sin(angle) * speed,
|
|
24
|
+
size: 2 + Math.random() * 4,
|
|
25
|
+
life: 80 + Math.random() * 40,
|
|
26
|
+
startColor: i % 2 === 0 ? "#ff6b6b" : "#ffd93d",
|
|
27
|
+
endColor: i % 3 === 0 ? "#ffffff" : "#6c5ce7",
|
|
28
|
+
textureType: i % 5 === 0 ? "star" : "circle",
|
|
29
|
+
rotationVelocity: -0.03 + Math.random() * 0.06,
|
|
30
|
+
secondaryEligible: false
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return particles;
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function createParticles(canvasData) {
|
|
2
|
+
const particles = [];
|
|
3
|
+
const count = 360;
|
|
4
|
+
const scale = 0.22; // 控制心形整体大小
|
|
5
|
+
|
|
6
|
+
for (let i = 0; i < count; i += 1) {
|
|
7
|
+
const t = (i / count) * Math.PI * 2;
|
|
8
|
+
|
|
9
|
+
// 笛卡尔心形参数方程(标准公式)
|
|
10
|
+
const dx = 16 * Math.pow(Math.sin(t), 3);
|
|
11
|
+
const dy = 13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t);
|
|
12
|
+
|
|
13
|
+
// 取反dy使得心形朝上,并乘以缩放系数作为速度
|
|
14
|
+
const vx = dx * scale;
|
|
15
|
+
const vy = -dy * scale;
|
|
16
|
+
|
|
17
|
+
particles.push({
|
|
18
|
+
x: canvasData.x,
|
|
19
|
+
y: canvasData.y,
|
|
20
|
+
vx: vx,
|
|
21
|
+
vy: vy,
|
|
22
|
+
size: 2.5 + Math.random() * 3.5,
|
|
23
|
+
life: 90 + Math.random() * 50,
|
|
24
|
+
startColor: i % 2 === 0 ? "#ff4757" : "#ff6b81",
|
|
25
|
+
endColor: i % 3 === 0 ? "#ffcccc" : "#c0392b",
|
|
26
|
+
textureType: "circle", // 心形适合全部用圆形,更柔美
|
|
27
|
+
rotationVelocity: 0,
|
|
28
|
+
secondaryEligible: false
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
config: {
|
|
34
|
+
gravity: 0,
|
|
35
|
+
},
|
|
36
|
+
particles
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
function createParticles(canvasData) {
|
|
2
|
+
const particles = [];
|
|
3
|
+
const total = 360;
|
|
4
|
+
const circleCount = 120; // 用于内圈
|
|
5
|
+
const rayCount = total - circleCount; // 用于射线
|
|
6
|
+
const numRays = 24; // 射线数量
|
|
7
|
+
const particlesPerRay = Math.floor(rayCount / numRays);
|
|
8
|
+
|
|
9
|
+
// ----- 1. 内圈粒子(核心发光圆)-----
|
|
10
|
+
for (let i = 0; i < circleCount; i += 1) {
|
|
11
|
+
const angle = (i / circleCount) * Math.PI * 2;
|
|
12
|
+
const speed = 1.8 + Math.random() * 1.2; // 低速,形成饱满圆圈
|
|
13
|
+
|
|
14
|
+
particles.push({
|
|
15
|
+
x: canvasData.x,
|
|
16
|
+
y: canvasData.y,
|
|
17
|
+
vx: Math.cos(angle) * speed,
|
|
18
|
+
vy: Math.sin(angle) * speed,
|
|
19
|
+
size: 3 + Math.random() * 5,
|
|
20
|
+
life: 60 + Math.random() * 30,
|
|
21
|
+
startColor: "#ffda79",
|
|
22
|
+
endColor: "#ffb142",
|
|
23
|
+
textureType: "circle",
|
|
24
|
+
rotationVelocity: 0,
|
|
25
|
+
secondaryEligible: false
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ----- 2. 射线粒子(光芒)-----
|
|
30
|
+
for (let j = 0; j < numRays; j += 1) {
|
|
31
|
+
const baseAngle = (j / numRays) * Math.PI * 2;
|
|
32
|
+
// 每条射线填充多个粒子,形成粗细和长度变化
|
|
33
|
+
for (let k = 0; k < particlesPerRay; k += 1) {
|
|
34
|
+
// 在基础角度附近加微小随机偏移,让射线有自然发散感
|
|
35
|
+
const angleOffset = (Math.random() - 0.5) * 0.12;
|
|
36
|
+
const angle = baseAngle + angleOffset;
|
|
37
|
+
|
|
38
|
+
// 速度随机,形成长短不一的芒刺
|
|
39
|
+
const speed = 4.5 + Math.random() * 8.5;
|
|
40
|
+
|
|
41
|
+
particles.push({
|
|
42
|
+
x: canvasData.x,
|
|
43
|
+
y: canvasData.y,
|
|
44
|
+
vx: Math.cos(angle) * speed,
|
|
45
|
+
vy: Math.sin(angle) * speed,
|
|
46
|
+
size: 1.5 + Math.random() * 2.5,
|
|
47
|
+
life: 70 + Math.random() * 60,
|
|
48
|
+
startColor: Math.random() > 0.3 ? "#fff5b0" : "#ff9f43",
|
|
49
|
+
endColor: "#feca57",
|
|
50
|
+
textureType: "circle",
|
|
51
|
+
rotationVelocity: 0,
|
|
52
|
+
secondaryEligible: false
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
config: {
|
|
59
|
+
gravity: 0,
|
|
60
|
+
},
|
|
61
|
+
particles
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function createParticles(canvasData) {
|
|
2
|
+
const particles = [];
|
|
3
|
+
const layers = 7;
|
|
4
|
+
const colors = [
|
|
5
|
+
"#FF0000", // 红
|
|
6
|
+
"#FF7F00", // 橙
|
|
7
|
+
"#FFFF00", // 黄
|
|
8
|
+
"#00FF00", // 绿
|
|
9
|
+
"#0000FF", // 蓝
|
|
10
|
+
"#4B0082", // 靛
|
|
11
|
+
"#8B00FF" // 紫
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const particlesPerLayer = 55; // 每层粒子数量,总数为385
|
|
15
|
+
const baseRadius = 20;
|
|
16
|
+
const radiusStep = 15;
|
|
17
|
+
const speedFactor = 0.10; // 速度 = 半径 * 该系数,保持同步扩张
|
|
18
|
+
|
|
19
|
+
for (let layer = 0; layer < layers; layer += 1) {
|
|
20
|
+
const radius = baseRadius + layer * radiusStep;
|
|
21
|
+
const color = colors[layer];
|
|
22
|
+
// 为增加层次感,起始色与结束色用同色系深浅搭配
|
|
23
|
+
const endColor = layer % 2 === 0 ? "#ffffff" : color;
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < particlesPerLayer; i += 1) {
|
|
26
|
+
const angle = (i / particlesPerLayer) * Math.PI * 2;
|
|
27
|
+
const speed = radius * speedFactor;
|
|
28
|
+
|
|
29
|
+
particles.push({
|
|
30
|
+
x: canvasData.x,
|
|
31
|
+
y: canvasData.y,
|
|
32
|
+
vx: Math.cos(angle) * speed,
|
|
33
|
+
vy: Math.sin(angle) * speed,
|
|
34
|
+
size: 2.5 + Math.random() * 3,
|
|
35
|
+
life: 100 + Math.random() * 40,
|
|
36
|
+
startColor: color,
|
|
37
|
+
endColor: endColor,
|
|
38
|
+
textureType: i % 4 === 0 ? "star" : "circle", // 少量星形点缀
|
|
39
|
+
rotationVelocity: -0.02 + Math.random() * 0.04,
|
|
40
|
+
secondaryEligible: false
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
config: {
|
|
47
|
+
gravity: 0,
|
|
48
|
+
},
|
|
49
|
+
particles
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function createParticles(canvasData) {
|
|
2
|
+
const particles = [];
|
|
3
|
+
const count = 12;
|
|
4
|
+
const speed = 7.2;
|
|
5
|
+
|
|
6
|
+
for (let i = 0; i < count; i += 1) {
|
|
7
|
+
const angle = -Math.PI / 2 + (i / count) * Math.PI * 2;
|
|
8
|
+
const isRed = i % 2 === 0;
|
|
9
|
+
|
|
10
|
+
particles.push({
|
|
11
|
+
x: canvasData.x,
|
|
12
|
+
y: canvasData.y,
|
|
13
|
+
vx: Math.cos(angle) * speed,
|
|
14
|
+
vy: Math.sin(angle) * speed,
|
|
15
|
+
size: 5,
|
|
16
|
+
life: 76,
|
|
17
|
+
startColor: isRed ? "#ff2d55" : "#2f80ff",
|
|
18
|
+
endColor: isRed ? "#ff8fa3" : "#8ec5ff",
|
|
19
|
+
textureType: "circle",
|
|
20
|
+
rotationVelocity: 0,
|
|
21
|
+
secondaryEligible: true,
|
|
22
|
+
secondaryBurstConfig: {
|
|
23
|
+
particleCount: 42,
|
|
24
|
+
particleMinSize: 2,
|
|
25
|
+
particleMaxSize: 4.2,
|
|
26
|
+
particleLife: 54,
|
|
27
|
+
initialSpeedMin: 1.7,
|
|
28
|
+
initialSpeedMax: 5.2,
|
|
29
|
+
colorStart: "#fff4a3",
|
|
30
|
+
colorEnd: "#ffffff",
|
|
31
|
+
useRandomColors: false,
|
|
32
|
+
textureType: "circle",
|
|
33
|
+
explosionShape: "sphere",
|
|
34
|
+
glowIntensity: 1.05,
|
|
35
|
+
enableTrails: true,
|
|
36
|
+
fadeTrail: 0.14,
|
|
37
|
+
flicker: true,
|
|
38
|
+
enableSecondaryBurst: false
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
config: {
|
|
45
|
+
gravity: 0.08,
|
|
46
|
+
damping: 0.988,
|
|
47
|
+
enableTrails: true,
|
|
48
|
+
glowIntensity: 1,
|
|
49
|
+
fadeTrail: 0.12,
|
|
50
|
+
flicker: true,
|
|
51
|
+
enableSecondaryBurst: true
|
|
52
|
+
},
|
|
53
|
+
particles
|
|
54
|
+
};
|
|
55
|
+
}
|
package/src/app.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { builtInPresets, defaultConfig, sanitizeConfig } from "./config/defaults.js";
|
|
2
|
+
import { FireworksSimulator } from "./core/FireworksSimulator.js";
|
|
3
|
+
|
|
4
|
+
export { FireworksSimulator, builtInPresets, defaultConfig, sanitizeConfig };
|
|
5
|
+
|
|
6
|
+
export function create(canvas, config = {}) {
|
|
7
|
+
return new FireworksSimulator(normalizeOptions(canvas, config));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function init(canvas, config = {}) {
|
|
11
|
+
return create(canvas, config);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const SparkFireworks = {
|
|
15
|
+
create,
|
|
16
|
+
init,
|
|
17
|
+
FireworksSimulator,
|
|
18
|
+
builtInPresets,
|
|
19
|
+
defaultConfig,
|
|
20
|
+
sanitizeConfig
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (typeof window !== "undefined") {
|
|
24
|
+
window.SparkFireworks = SparkFireworks;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default SparkFireworks;
|
|
28
|
+
|
|
29
|
+
function normalizeOptions(canvas, config) {
|
|
30
|
+
const canvasOption = typeof canvas === "string" ? { canvasId: canvas } : { canvas };
|
|
31
|
+
const looksLikeFullOptions =
|
|
32
|
+
config &&
|
|
33
|
+
typeof config === "object" &&
|
|
34
|
+
("config" in config ||
|
|
35
|
+
"onConfigChange" in config ||
|
|
36
|
+
"onFpsChange" in config ||
|
|
37
|
+
"onLowFps" in config ||
|
|
38
|
+
"clickToLaunch" in config ||
|
|
39
|
+
"keyboard" in config ||
|
|
40
|
+
"autoResize" in config ||
|
|
41
|
+
"pauseOnHidden" in config ||
|
|
42
|
+
"initialLaunch" in config ||
|
|
43
|
+
"advancedMode" in config ||
|
|
44
|
+
"createParticles" in config);
|
|
45
|
+
|
|
46
|
+
return looksLikeFullOptions
|
|
47
|
+
? { ...config, ...canvasOption }
|
|
48
|
+
: { ...canvasOption, config };
|
|
49
|
+
}
|