@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
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# SparkFireworks 性能优化方案
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
当前项目在粒子数量较多、开启拖尾、辉光、复杂形状或自动连续发射时容易掉帧。主要原因不是单个算法复杂,而是多个高频开销叠加:
|
|
6
|
+
|
|
7
|
+
- 粒子生成时线性扫描粒子池。
|
|
8
|
+
- 每帧更新和绘制都会遍历整个粒子池。
|
|
9
|
+
- 轨迹每帧创建对象并使用 `shift()` 移除旧点,容易触发 GC。
|
|
10
|
+
- 每个粒子绘制都执行 `save/restore`、颜色混合、CSS 字符串拼接、阴影、路径绘制。
|
|
11
|
+
- `shadowBlur`、`globalCompositeOperation = "lighter"`、星形/心形路径在 Canvas 2D 中成本较高。
|
|
12
|
+
- 低 FPS 自适应只降低未来爆炸粒子数,不能快速缓解已经存在的绘制压力。
|
|
13
|
+
|
|
14
|
+
目标是在保持现有效果和 API 基本兼容的前提下,让高粒子数场景更稳定,并为后续 WebGL/Worker 改造留出结构空间。
|
|
15
|
+
|
|
16
|
+
## 优化目标
|
|
17
|
+
|
|
18
|
+
- 默认配置保持 60 FPS 附近。
|
|
19
|
+
- 1000-1400 活跃粒子时减少明显卡顿和 GC 抖动。
|
|
20
|
+
- 低端设备或高 DPI 屏幕下能自动降级效果。
|
|
21
|
+
- 不破坏 `FireworksSimulator`、`SparkFireworks.create()`、高级脚本粒子格式等公开 API。
|
|
22
|
+
- 优化应可分阶段落地,每阶段都能独立验证。
|
|
23
|
+
|
|
24
|
+
## 当前热点定位
|
|
25
|
+
|
|
26
|
+
### 粒子池
|
|
27
|
+
|
|
28
|
+
文件:`src/core/ParticleSystem.js`
|
|
29
|
+
|
|
30
|
+
现状:
|
|
31
|
+
|
|
32
|
+
- `spawn()` 使用 `this.pool.find((item) => !item.active)`,每创建一个粒子都可能扫描整个池。
|
|
33
|
+
- `update()` 和 `draw()` 遍历完整 `pool`,即使大部分粒子不活跃也会被检查。
|
|
34
|
+
- `addExplosion()` 批量生成粒子时,上述扫描会被放大。
|
|
35
|
+
|
|
36
|
+
风险:
|
|
37
|
+
|
|
38
|
+
- 一次 1000 粒子的爆炸接近 O(n * poolSize)。
|
|
39
|
+
- 多次爆炸叠加时,主线程会在粒子生成阶段产生明显尖峰。
|
|
40
|
+
|
|
41
|
+
### 粒子更新
|
|
42
|
+
|
|
43
|
+
文件:`src/core/Particle.js`
|
|
44
|
+
|
|
45
|
+
现状:
|
|
46
|
+
|
|
47
|
+
- 每粒子每帧调用 `Math.pow(runtimeConfig.damping, step)`。
|
|
48
|
+
- 开启拖尾时每帧 `this.trail.push({ x, y, life })`,长度超过 8 后 `shift()`。
|
|
49
|
+
- 粒子 `reset()` 中直接替换 `this.trail = []`,复用粒子对象时仍会持续制造新数组和轨迹点对象。
|
|
50
|
+
|
|
51
|
+
风险:
|
|
52
|
+
|
|
53
|
+
- 大量短生命周期对象造成 GC 抖动。
|
|
54
|
+
- `shift()` 会移动数组元素,粒子多时成本明显。
|
|
55
|
+
|
|
56
|
+
### 粒子绘制
|
|
57
|
+
|
|
58
|
+
文件:`src/core/Particle.js`
|
|
59
|
+
|
|
60
|
+
现状:
|
|
61
|
+
|
|
62
|
+
- 每粒子每帧执行 `mixColor()`、`rgbToCss()`,产生新颜色对象或字符串。
|
|
63
|
+
- 每粒子绘制都 `ctx.save()` / `ctx.restore()`。
|
|
64
|
+
- 每粒子都设置 `shadowColor` 和 `shadowBlur`。
|
|
65
|
+
- 圆形使用 `arc()`,星形和心形每次都重新构建路径。
|
|
66
|
+
- 拖尾每段线单独 `beginPath()`、`stroke()`,并为每段生成颜色字符串。
|
|
67
|
+
|
|
68
|
+
风险:
|
|
69
|
+
|
|
70
|
+
- Canvas 2D 绘制调用过多,主线程容易被 draw call 和状态切换占满。
|
|
71
|
+
- `shadowBlur` 在高粒子数下非常昂贵。
|
|
72
|
+
- 复杂形状和旋转进一步放大路径计算成本。
|
|
73
|
+
|
|
74
|
+
### 渲染器和像素比
|
|
75
|
+
|
|
76
|
+
文件:`src/core/Renderer.js`
|
|
77
|
+
|
|
78
|
+
现状:
|
|
79
|
+
|
|
80
|
+
- `pixelRatio` 固定取 `Math.min(devicePixelRatio, 2)`。
|
|
81
|
+
- 高 DPI 屏幕上绘制像素数可能是 CSS 面积的 4 倍。
|
|
82
|
+
- 低 FPS 时不会动态降低像素比。
|
|
83
|
+
|
|
84
|
+
风险:
|
|
85
|
+
|
|
86
|
+
- 同样粒子数在高 DPI 屏上会更卡。
|
|
87
|
+
- 自适应策略无法处理填充率瓶颈。
|
|
88
|
+
|
|
89
|
+
### 低 FPS 自适应
|
|
90
|
+
|
|
91
|
+
文件:`src/core/FireworksSimulator.js`
|
|
92
|
+
|
|
93
|
+
现状:
|
|
94
|
+
|
|
95
|
+
- 低 FPS 持续后只将 `config.particleCount` 乘以 0.72。
|
|
96
|
+
- 已经活跃的粒子不会被裁剪或缩短寿命。
|
|
97
|
+
- 不会降低辉光、拖尾、闪烁、复杂形状或像素比。
|
|
98
|
+
|
|
99
|
+
风险:
|
|
100
|
+
|
|
101
|
+
- 自适应生效慢。
|
|
102
|
+
- 卡顿来自绘制时,仅降低未来粒子数不够。
|
|
103
|
+
- 低帧时不能直接大步推进物理状态,否则粒子位置会突然跨越;应将大时间片拆成多个小步更新。
|
|
104
|
+
- 连续点击时升空壳队列过长会让爆炸延后;高负载下需要允许直接在目标点爆炸。
|
|
105
|
+
|
|
106
|
+
## 分阶段实施计划
|
|
107
|
+
|
|
108
|
+
### 阶段 1:低风险结构优化
|
|
109
|
+
|
|
110
|
+
目标:减少无效遍历和生成尖峰,不改变视觉效果。
|
|
111
|
+
|
|
112
|
+
任务:
|
|
113
|
+
|
|
114
|
+
1. 将 `ParticleSystem` 从完整池遍历改为 `activeParticles` + `freeParticles`。
|
|
115
|
+
2. `spawn()` 从 `freeParticles.pop()` 取粒子,失败时直接丢弃。
|
|
116
|
+
3. `update()` 只遍历活跃数组;粒子死亡后用 swap-remove 移除。
|
|
117
|
+
4. `draw()` 只绘制活跃数组。
|
|
118
|
+
5. `clear()` 将活跃粒子归还空闲栈。
|
|
119
|
+
|
|
120
|
+
建议结构:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
class ParticleSystem {
|
|
124
|
+
constructor(factory, maxParticles) {
|
|
125
|
+
this.pool = Array.from({ length: maxParticles }, () => new Particle());
|
|
126
|
+
this.freeParticles = [...this.pool];
|
|
127
|
+
this.activeParticles = [];
|
|
128
|
+
this.activeCount = 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
验收:
|
|
134
|
+
|
|
135
|
+
- 同样配置下视觉表现基本一致。
|
|
136
|
+
- 连续点击发射时生成阶段不再明显卡一下。
|
|
137
|
+
- `activeCount` 与实际活跃数组长度一致。
|
|
138
|
+
- `npm run build` 通过。
|
|
139
|
+
|
|
140
|
+
### 阶段 2:轨迹内存优化
|
|
141
|
+
|
|
142
|
+
目标:消除拖尾造成的大量对象分配和数组移动。
|
|
143
|
+
|
|
144
|
+
任务:
|
|
145
|
+
|
|
146
|
+
1. 在 `Particle` 构造或初始化时创建固定长度轨迹数组。
|
|
147
|
+
2. 使用环形索引写入轨迹点,不再 `push()` 对象和 `shift()`。
|
|
148
|
+
3. `reset()` 只重置轨迹长度和写入索引。
|
|
149
|
+
4. `drawTrail()` 按时间顺序读取环形缓冲。
|
|
150
|
+
|
|
151
|
+
建议数据结构:
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
this.trailX = new Float32Array(8);
|
|
155
|
+
this.trailY = new Float32Array(8);
|
|
156
|
+
this.trailLength = 0;
|
|
157
|
+
this.trailCursor = 0;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
验收:
|
|
161
|
+
|
|
162
|
+
- 开启拖尾时视觉表现接近原效果。
|
|
163
|
+
- Performance 面板中 GC 频率下降。
|
|
164
|
+
- 高粒子数拖尾场景帧时间更平稳。
|
|
165
|
+
|
|
166
|
+
### 阶段 3:绘制路径和颜色缓存
|
|
167
|
+
|
|
168
|
+
目标:减少每粒子每帧的 Canvas 状态切换、路径构建和字符串创建。
|
|
169
|
+
|
|
170
|
+
任务:
|
|
171
|
+
|
|
172
|
+
1. 在 `Particle.reset()` 时将 `startColor`、`endColor` 解析为 RGB 数字字段。
|
|
173
|
+
2. 将颜色变化量按有限档位量化,例如 32 或 64 档。
|
|
174
|
+
3. 缓存 `rgba(r,g,b,a)` 字符串,避免每粒子每帧重复拼接。
|
|
175
|
+
4. 圆形、方形、星形、心形改为离屏 sprite 缓存。
|
|
176
|
+
5. 主画布绘制时优先使用 `drawImage()`,仅在缺少缓存时回退路径绘制。
|
|
177
|
+
|
|
178
|
+
建议新增模块:
|
|
179
|
+
|
|
180
|
+
- `src/core/ParticleSpriteCache.js`
|
|
181
|
+
- `src/core/ColorRampCache.js`
|
|
182
|
+
|
|
183
|
+
缓存 key 示例:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
textureType:sizeBucket:colorBucket:alphaBucket:glowBucket
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
注意:
|
|
190
|
+
|
|
191
|
+
- 先对 `circle` 和 `square` 做 sprite 缓存,收益最大且风险最低。
|
|
192
|
+
- `star` 和 `heart` 可以第二步接入。
|
|
193
|
+
- 如果旋转只影响复杂形状,可在高粒子数时关闭或降低旋转更新频率。
|
|
194
|
+
|
|
195
|
+
验收:
|
|
196
|
+
|
|
197
|
+
- 默认圆形粒子下 draw call 仍多,但路径构建和阴影开销下降。
|
|
198
|
+
- 星形/心形效果保持可接受。
|
|
199
|
+
- sprite 缓存大小可控,不随时间无限增长。
|
|
200
|
+
|
|
201
|
+
### 阶段 4:高负载自适应降级
|
|
202
|
+
|
|
203
|
+
目标:在卡顿发生前主动降低昂贵效果。
|
|
204
|
+
|
|
205
|
+
任务:
|
|
206
|
+
|
|
207
|
+
1. 根据 `activeCount` 和 FPS 计算性能等级。
|
|
208
|
+
2. 性能等级影响运行时绘制参数,不直接覆盖用户配置。
|
|
209
|
+
3. 高负载时分级降低:
|
|
210
|
+
- `shadowBlur`
|
|
211
|
+
- `glowIntensity`
|
|
212
|
+
- `flicker`
|
|
213
|
+
- 粒子拖尾段数
|
|
214
|
+
- 复杂形状旋转
|
|
215
|
+
- `pixelRatio`
|
|
216
|
+
4. 低 FPS 持续时缩短部分活跃粒子的剩余寿命,快速降低压力。
|
|
217
|
+
|
|
218
|
+
建议等级:
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
level 0: 正常
|
|
222
|
+
level 1: activeCount > 600,降低 shadowBlur 和拖尾段数
|
|
223
|
+
level 2: activeCount > 900 或 FPS < 45,关闭单粒子 shadowBlur,保留 lighter
|
|
224
|
+
level 3: FPS < 35,pixelRatio 降到 1,关闭 flicker,缩短剩余寿命
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
建议新增运行时状态:
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
this.performanceLevel = 0;
|
|
231
|
+
this.renderQuality = {
|
|
232
|
+
pixelRatio: 1,
|
|
233
|
+
glowScale: 1,
|
|
234
|
+
trailSampleStep: 1,
|
|
235
|
+
useParticleShadow: true,
|
|
236
|
+
useFlicker: true
|
|
237
|
+
};
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
验收:
|
|
241
|
+
|
|
242
|
+
- 高负载时画面会略降级,但不会持续严重卡顿。
|
|
243
|
+
- 用户配置值不被静默改写,控制面板显示仍保持用户选择。
|
|
244
|
+
- 低 FPS 事件仍能正常触发。
|
|
245
|
+
|
|
246
|
+
### 阶段 5:像素比和画布填充率优化
|
|
247
|
+
|
|
248
|
+
目标:降低高 DPI 屏幕和大画布上的像素填充压力。
|
|
249
|
+
|
|
250
|
+
任务:
|
|
251
|
+
|
|
252
|
+
1. `Renderer.resize()` 支持传入目标像素比。
|
|
253
|
+
2. 根据性能等级动态调整 `pixelRatio`。注意:运行中直接 resize canvas 会清空 backing store,容易造成爆炸瞬间闪屏;该项需要延迟到无活跃粒子时执行,或使用双缓冲方案。
|
|
254
|
+
3. 避免每次 `setConfig()` 都强制完整背景重绘,只有背景相关配置变化时才重绘。
|
|
255
|
+
4. 对静态背景使用离屏缓存,拖尾模式下只覆盖半透明黑色。
|
|
256
|
+
|
|
257
|
+
验收:
|
|
258
|
+
|
|
259
|
+
- Retina 屏上低负载仍清晰,高负载时能自动降像素比。
|
|
260
|
+
- 调整非背景参数不会触发不必要的完整背景重绘。
|
|
261
|
+
|
|
262
|
+
### 阶段 6:长期方向
|
|
263
|
+
|
|
264
|
+
如果后续需要更高粒子上限,可以考虑更大的结构改造:
|
|
265
|
+
|
|
266
|
+
1. WebGL 或 WebGPU 粒子渲染
|
|
267
|
+
- 使用点精灵或实例化绘制。
|
|
268
|
+
- 粒子位置、速度、寿命进入 typed array。
|
|
269
|
+
- 适合 5000+ 粒子规模。
|
|
270
|
+
|
|
271
|
+
2. Worker 更新粒子状态
|
|
272
|
+
- 主线程只负责渲染和输入。
|
|
273
|
+
- 粒子位置通过 transferable typed array 或双缓冲同步。
|
|
274
|
+
- 需要权衡同步成本,建议在 Canvas 2D 优化完成后再评估。
|
|
275
|
+
|
|
276
|
+
3. 完整数据导向重写
|
|
277
|
+
- 使用 SoA:`x[]`、`y[]`、`vx[]`、`vy[]`、`life[]`。
|
|
278
|
+
- 减少对象访问和方法调用。
|
|
279
|
+
- 代码可读性下降,适合作为高性能模式而不是默认实现。
|
|
280
|
+
|
|
281
|
+
## 推荐实施顺序
|
|
282
|
+
|
|
283
|
+
优先级从高到低:
|
|
284
|
+
|
|
285
|
+
1. `ParticleSystem` 活跃数组和空闲栈。
|
|
286
|
+
2. 轨迹环形缓冲。
|
|
287
|
+
3. 圆形和方形 sprite 缓存。
|
|
288
|
+
4. 高负载运行时降级。
|
|
289
|
+
5. 动态像素比,需避免运行中 resize 闪屏。
|
|
290
|
+
6. 星形和心形 sprite 缓存。
|
|
291
|
+
7. WebGL/Worker 可行性验证。
|
|
292
|
+
|
|
293
|
+
不建议第一阶段就做 WebGL 或 Worker。当前瓶颈仍有大量 Canvas 2D 和对象分配层面的低风险优化空间,先处理这些能更快获得收益,也能保留现有 API 和代码结构。
|
|
294
|
+
|
|
295
|
+
## 性能验证方案
|
|
296
|
+
|
|
297
|
+
### 手动测试场景
|
|
298
|
+
|
|
299
|
+
1. 默认配置,连续点击 10 次。
|
|
300
|
+
2. `particleCount = 1000`,关闭二次爆炸。
|
|
301
|
+
3. `particleCount = 1000`,开启拖尾、辉光、闪烁。
|
|
302
|
+
4. `textureType = "star"` 和 `"heart"` 分别测试。
|
|
303
|
+
5. 开启 `autoLaunch`,`autoLaunchIntervalSeconds = 0.25`(兼容旧配置 `autoInterval = 250`),运行 30 秒。
|
|
304
|
+
6. 高 DPI 屏幕或浏览器缩放 150% 下测试。
|
|
305
|
+
|
|
306
|
+
### 观察指标
|
|
307
|
+
|
|
308
|
+
- FPS 是否稳定在 45-60。
|
|
309
|
+
- Performance 录制中是否存在明显 GC 尖峰。
|
|
310
|
+
- 粒子生成瞬间是否有长任务。
|
|
311
|
+
- Canvas 绘制时间是否随优化下降。
|
|
312
|
+
- 低 FPS 降级是否及时恢复流畅。
|
|
313
|
+
|
|
314
|
+
### 建议添加调试指标
|
|
315
|
+
|
|
316
|
+
可以在性能面板或开发模式日志中加入:
|
|
317
|
+
|
|
318
|
+
```text
|
|
319
|
+
FPS
|
|
320
|
+
activeParticles
|
|
321
|
+
freeParticles
|
|
322
|
+
performanceLevel
|
|
323
|
+
pixelRatio
|
|
324
|
+
drawMode: path | sprite
|
|
325
|
+
spriteCacheSize
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## 兼容性约束
|
|
329
|
+
|
|
330
|
+
- 不改变公开构造方式:`SparkFireworks.create(canvas, configOrOptions)`。
|
|
331
|
+
- 不改变高级脚本返回粒子的字段语义。
|
|
332
|
+
- `particleCount` 仍表示单次爆炸期望粒子数,但实际生成数应受池容量限制。
|
|
333
|
+
- 性能降级只影响运行时绘制质量,不应永久覆盖用户配置。
|
|
334
|
+
- 现有预设应继续可用。
|
|
335
|
+
|
|
336
|
+
## 风险和回滚点
|
|
337
|
+
|
|
338
|
+
### 活跃数组改造
|
|
339
|
+
|
|
340
|
+
风险:
|
|
341
|
+
|
|
342
|
+
- 粒子死亡时移除逻辑错误可能导致漏绘、重复绘制或 `activeCount` 不准。
|
|
343
|
+
|
|
344
|
+
回滚:
|
|
345
|
+
|
|
346
|
+
- 保留原 `pool` 字段,必要时恢复完整池遍历。
|
|
347
|
+
|
|
348
|
+
### 轨迹环形缓冲
|
|
349
|
+
|
|
350
|
+
风险:
|
|
351
|
+
|
|
352
|
+
- 读取顺序错误会导致拖尾断裂或反向。
|
|
353
|
+
|
|
354
|
+
回滚:
|
|
355
|
+
|
|
356
|
+
- 保留原 `trail` 绘制路径,先用配置开关切换。
|
|
357
|
+
|
|
358
|
+
### Sprite 缓存
|
|
359
|
+
|
|
360
|
+
风险:
|
|
361
|
+
|
|
362
|
+
- 缓存 key 过细导致内存增长。
|
|
363
|
+
- 缓存 key 过粗导致颜色或尺寸跳变明显。
|
|
364
|
+
|
|
365
|
+
回滚:
|
|
366
|
+
|
|
367
|
+
- `Particle.draw()` 保留路径绘制回退。
|
|
368
|
+
|
|
369
|
+
### 动态像素比
|
|
370
|
+
|
|
371
|
+
风险:
|
|
372
|
+
|
|
373
|
+
- 频繁 resize 会导致画面闪烁或背景重绘成本上升。
|
|
374
|
+
|
|
375
|
+
回滚:
|
|
376
|
+
|
|
377
|
+
- 像素比调整加冷却时间,或只在低 FPS 持续超过阈值后调整。
|
|
378
|
+
|
|
379
|
+
## 第一批落地清单
|
|
380
|
+
|
|
381
|
+
第一批建议只做不会改变视觉输出的改造:
|
|
382
|
+
|
|
383
|
+
- [x] `ParticleSystem` 改成活跃数组 + 空闲栈。
|
|
384
|
+
- [x] `spawn()` 不再使用 `find()`。
|
|
385
|
+
- [x] `update()` 和 `draw()` 只遍历活跃粒子。
|
|
386
|
+
- [x] `clear()` 正确回收粒子。
|
|
387
|
+
- [x] 添加基础统计字段,便于观察活跃粒子和空闲粒子数量。
|
|
388
|
+
- [x] 运行 `npm run build`。
|
|
389
|
+
- [ ] 在默认配置和 1000 粒子配置下手动验证。
|
|
390
|
+
|
|
391
|
+
第二批再处理视觉层面的高收益优化:
|
|
392
|
+
|
|
393
|
+
- [x] 轨迹环形缓冲。
|
|
394
|
+
- [ ] 圆形 sprite 缓存。
|
|
395
|
+
- [x] 高负载关闭或降低单粒子 shadowBlur。
|
|
396
|
+
- [x] 低 FPS 时只改运行时质量,不直接覆盖用户配置。
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
<title>SparkFireworks Production Example</title>
|
|
7
|
+
<style>
|
|
8
|
+
html,
|
|
9
|
+
body {
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
margin: 0;
|
|
13
|
+
background: #050711;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
canvas {
|
|
17
|
+
display: block;
|
|
18
|
+
width: 100vw;
|
|
19
|
+
height: 100vh;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.toolbar {
|
|
23
|
+
position: fixed;
|
|
24
|
+
top: 16px;
|
|
25
|
+
left: 16px;
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
max-width: min(760px, calc(100vw - 32px));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
button {
|
|
33
|
+
min-height: 34px;
|
|
34
|
+
border: 1px solid rgba(255, 255, 255, 0.22);
|
|
35
|
+
border-radius: 6px;
|
|
36
|
+
padding: 0 12px;
|
|
37
|
+
background: rgba(10, 16, 24, 0.82);
|
|
38
|
+
color: #fff;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
button:hover {
|
|
43
|
+
border-color: rgba(72, 214, 194, 0.8);
|
|
44
|
+
background: rgba(72, 214, 194, 0.18);
|
|
45
|
+
}
|
|
46
|
+
</style>
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
<canvas id="fireworks"></canvas>
|
|
50
|
+
|
|
51
|
+
<div class="toolbar">
|
|
52
|
+
<button id="launchWithTrail">随机发射(带轨迹)</button>
|
|
53
|
+
<button id="launchWithoutTrail">随机发射(不带轨迹)</button>
|
|
54
|
+
<button id="launchAdvanced">高级模式发射</button>
|
|
55
|
+
<button id="start">开始自动播放</button>
|
|
56
|
+
<button id="stop">停止自动播放</button>
|
|
57
|
+
<button id="preset">爱心预设</button>
|
|
58
|
+
<button id="reset">初始化设置</button>
|
|
59
|
+
<button id="clear">清除粒子</button>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- 生产环境中先执行 npm run build:lib,再引入 dist-lib 里的 UMD 文件。 -->
|
|
63
|
+
<script src="../dist-lib/spark-fireworks.umd.cjs"></script>
|
|
64
|
+
<script>
|
|
65
|
+
const canvas = document.getElementById("fireworks");
|
|
66
|
+
|
|
67
|
+
const fullConfig = {
|
|
68
|
+
// 粒子基础
|
|
69
|
+
particleCount: 260,
|
|
70
|
+
particleMinSize: 2.5,
|
|
71
|
+
particleMaxSize: 7,
|
|
72
|
+
sizeEndMultiplier: 0.35,
|
|
73
|
+
|
|
74
|
+
// 运动学
|
|
75
|
+
gravity: 0.18,
|
|
76
|
+
damping: 0.982,
|
|
77
|
+
initialSpeedMin: 2.8,
|
|
78
|
+
initialSpeedMax: 8.2,
|
|
79
|
+
particleLife: 94,
|
|
80
|
+
rotationSpeed: 0.025,
|
|
81
|
+
colorShiftRate: 0.035,
|
|
82
|
+
|
|
83
|
+
// 视觉
|
|
84
|
+
colorStart: "#ffd166",
|
|
85
|
+
colorEnd: "#ef476f",
|
|
86
|
+
useRandomColors: false,
|
|
87
|
+
textureType: "circle",
|
|
88
|
+
glowIntensity: 0.75,
|
|
89
|
+
|
|
90
|
+
// 特效
|
|
91
|
+
explosionShape: "sphere",
|
|
92
|
+
enableSecondaryBurst: false,
|
|
93
|
+
secondaryBurstRatio: 0.2,
|
|
94
|
+
enableTrails: true,
|
|
95
|
+
showLaunchTrail: true,
|
|
96
|
+
windEnabled: false,
|
|
97
|
+
windX: 0,
|
|
98
|
+
windY: 0,
|
|
99
|
+
flicker: true,
|
|
100
|
+
fadeTrail: 0.18,
|
|
101
|
+
|
|
102
|
+
// 自动播放
|
|
103
|
+
autoLaunch: false,
|
|
104
|
+
autoLaunchIntervalSeconds: 0.9,
|
|
105
|
+
autoInterval: 900,
|
|
106
|
+
|
|
107
|
+
// 背景
|
|
108
|
+
backgroundType: "starfield",
|
|
109
|
+
backgroundColor: "#060912",
|
|
110
|
+
backgroundColor2: "#10243a",
|
|
111
|
+
backgroundImageMode: "cover"
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const fireworks = SparkFireworks.create(canvas, {
|
|
115
|
+
config: fullConfig,
|
|
116
|
+
advancedMode: false,
|
|
117
|
+
createParticles(canvasData) {
|
|
118
|
+
const particles = [];
|
|
119
|
+
const count = 320;
|
|
120
|
+
const centerX = canvasData.x;
|
|
121
|
+
const centerY = canvasData.y;
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < count; i += 1) {
|
|
124
|
+
const angle = (i / count) * Math.PI * 2;
|
|
125
|
+
const wave = 0.65 + Math.abs(Math.sin(angle * 4)) * 0.75;
|
|
126
|
+
const speed = wave * (3.5 + Math.random() * 6);
|
|
127
|
+
|
|
128
|
+
particles.push({
|
|
129
|
+
x: centerX,
|
|
130
|
+
y: centerY,
|
|
131
|
+
vx: Math.cos(angle) * speed,
|
|
132
|
+
vy: Math.sin(angle) * speed,
|
|
133
|
+
size: 2 + Math.random() * 5,
|
|
134
|
+
life: 70 + Math.random() * 55,
|
|
135
|
+
startColor: i % 2 === 0 ? "#48d6c2" : "#ffcf5a",
|
|
136
|
+
endColor: i % 3 === 0 ? "#ffffff" : "#ef476f",
|
|
137
|
+
textureType: i % 5 === 0 ? "star" : "circle",
|
|
138
|
+
rotationVelocity: -0.04 + Math.random() * 0.08,
|
|
139
|
+
secondaryEligible: false
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log(particles);
|
|
144
|
+
|
|
145
|
+
return particles;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
function getRandomLaunchPoint() {
|
|
150
|
+
const rect = canvas.getBoundingClientRect();
|
|
151
|
+
return {
|
|
152
|
+
x: rect.width * (0.16 + Math.random() * 0.68),
|
|
153
|
+
y: rect.height * (0.16 + Math.random() * 0.46)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function launchRandomWithTrail() {
|
|
158
|
+
const point = getRandomLaunchPoint();
|
|
159
|
+
fireworks.setConfig({
|
|
160
|
+
...fullConfig,
|
|
161
|
+
showLaunchTrail: true,
|
|
162
|
+
enableTrails: true
|
|
163
|
+
});
|
|
164
|
+
fireworks.launch(point.x, point.y, { withShell: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function launchRandomWithoutTrail() {
|
|
168
|
+
const point = getRandomLaunchPoint();
|
|
169
|
+
fireworks.setConfig({
|
|
170
|
+
...fullConfig,
|
|
171
|
+
showLaunchTrail: false,
|
|
172
|
+
enableTrails: false
|
|
173
|
+
});
|
|
174
|
+
fireworks.launch(point.x, point.y, { withShell: false });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function launchAdvancedMode() {
|
|
178
|
+
const point = getRandomLaunchPoint();
|
|
179
|
+
fireworks.setAdvancedMode(true);
|
|
180
|
+
fireworks.setConfig({
|
|
181
|
+
...fullConfig,
|
|
182
|
+
showLaunchTrail: false,
|
|
183
|
+
enableTrails: true,
|
|
184
|
+
textureType: "star",
|
|
185
|
+
colorStart: "#48d6c2",
|
|
186
|
+
colorEnd: "#ef476f",
|
|
187
|
+
fadeTrail: 0.11
|
|
188
|
+
});
|
|
189
|
+
fireworks.launch(point.x, point.y, { withShell: false });
|
|
190
|
+
fireworks.setAdvancedMode(false);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
document.getElementById("launchWithTrail").onclick = launchRandomWithTrail;
|
|
194
|
+
document.getElementById("launchWithoutTrail").onclick = launchRandomWithoutTrail;
|
|
195
|
+
document.getElementById("launchAdvanced").onclick = launchAdvancedMode;
|
|
196
|
+
document.getElementById("start").onclick = () => fireworks.startAutoPlay();
|
|
197
|
+
document.getElementById("stop").onclick = () => fireworks.stopAutoPlay();
|
|
198
|
+
document.getElementById("preset").onclick = () => fireworks.selectPreset("heart");
|
|
199
|
+
document.getElementById("reset").onclick = () => fireworks.resetConfig(fullConfig);
|
|
200
|
+
document.getElementById("clear").onclick = () => fireworks.clearParticles();
|
|
201
|
+
</script>
|
|
202
|
+
</body>
|
|
203
|
+
</html>
|
package/favicon.jpg
ADDED
|
Binary file
|
package/guide.html
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
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>SparkFireworks 使用指导</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">
|
|
21
|
+
<nav class="site-nav" aria-label="站点导航">
|
|
22
|
+
<a class="brand" href="/">SparkFireworks</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
|
+
<header class="page-title">
|
|
32
|
+
<h1>使用指导</h1>
|
|
33
|
+
<p>这页面向普通使用者,介绍功能演示页里每一类控件的作用,以及如何稳定地调出想要的烟花效果。</p>
|
|
34
|
+
</header>
|
|
35
|
+
|
|
36
|
+
<section class="doc-section">
|
|
37
|
+
<h2>快速开始</h2>
|
|
38
|
+
<ol>
|
|
39
|
+
<li>打开 <a href="/demo.html">功能演示页</a>。</li>
|
|
40
|
+
<li>点击画布任意位置发射烟花。</li>
|
|
41
|
+
<li>在右侧面板调整颜色、形状、粒子数量、拖尾、背景等参数。</li>
|
|
42
|
+
<li>使用顶部预设下拉框快速切换内置效果。</li>
|
|
43
|
+
</ol>
|
|
44
|
+
</section>
|
|
45
|
+
|
|
46
|
+
<section class="doc-section">
|
|
47
|
+
<h2>顶部工具栏</h2>
|
|
48
|
+
<ul>
|
|
49
|
+
<li><strong>预设选择:</strong>选择内置预设或保存到本地的配置。</li>
|
|
50
|
+
<li><strong>初始化设置:</strong>恢复默认配置。</li>
|
|
51
|
+
<li><strong>保存:</strong>把当前配置保存到浏览器本地。</li>
|
|
52
|
+
<li><strong>导出/导入:</strong>把配置保存成 JSON 文件,或从 JSON 文件恢复。</li>
|
|
53
|
+
<li><strong>分享:</strong>把当前配置写入链接,便于复制给其他人。</li>
|
|
54
|
+
<li><strong>自动:</strong>按自动间隔持续发射随机烟花。</li>
|
|
55
|
+
<li><strong>暂停:</strong>暂停或恢复动画。</li>
|
|
56
|
+
<li><strong>清除:</strong>清空当前画布上的粒子。</li>
|
|
57
|
+
</ul>
|
|
58
|
+
</section>
|
|
59
|
+
|
|
60
|
+
<section class="doc-section">
|
|
61
|
+
<h2>参数面板</h2>
|
|
62
|
+
<h3>样式</h3>
|
|
63
|
+
<p>控制粒子的起始颜色、结束颜色、随机颜色、粒子形状和辉光强度。辉光越高越亮,但绘制成本也更高。</p>
|
|
64
|
+
<h3>特效</h3>
|
|
65
|
+
<p>控制爆炸形状、二次爆炸、拖尾、升空轨迹、风场、闪烁和画面残影。拖尾和二次爆炸都会增加性能压力,应按设备能力酌情开启。</p>
|
|
66
|
+
<h3>粒子</h3>
|
|
67
|
+
<p>控制单次爆炸粒子数、初速度、阻尼、重力、生命周期、大小和自动播放间隔。粒子数量越高,画面越密集,性能压力也越大。</p>
|
|
68
|
+
<h3>背景</h3>
|
|
69
|
+
<p>支持纯色、线性渐变、径向渐变、星空和图片背景。上传图片后会自动切换到图片背景。</p>
|
|
70
|
+
</section>
|
|
71
|
+
|
|
72
|
+
<section class="doc-section">
|
|
73
|
+
<h2>调参建议</h2>
|
|
74
|
+
<ul>
|
|
75
|
+
<li>想要干净利落的爆炸:降低 `fadeTrail`,关闭粒子拖尾,使用圆形粒子。</li>
|
|
76
|
+
<li>想要浪漫柔和的效果:提高辉光,开启拖尾,降低重力和速度。</li>
|
|
77
|
+
<li>想要大规模烟花:提高粒子数,但谨慎开启二次爆炸和复杂形状。</li>
|
|
78
|
+
<li>帧率下降时:先降低粒子数量、关闭二次爆炸,再降低辉光和拖尾。</li>
|
|
79
|
+
</ul>
|
|
80
|
+
</section>
|
|
81
|
+
|
|
82
|
+
<section class="doc-section">
|
|
83
|
+
<h2>预设和配置</h2>
|
|
84
|
+
<p>内置预设适合快速体验不同风格。本地保存的预设只保存在当前浏览器;需要迁移到其他设备时,请使用导出和导入功能。</p>
|
|
85
|
+
<p class="note">高级脚本不会自动保存到普通预设里。需要复用脚本时,建议保存为独立 JS 文件或复制到高级指导页中的示例格式。</p>
|
|
86
|
+
</section>
|
|
87
|
+
</main>
|
|
88
|
+
</body>
|
|
89
|
+
</html>
|