新年来临之前,业务需求要求整一个烟花特效,对canvas研究不多,翻遍GitHub,找到了一个合眼缘的烟花效果,拿到源码了咱们来根据需求进行特制改造。 此库的效果展示,甚合我心,在情人节前夕,分享给各位有心人。
1.结合cursor达成业务需求
最终效果
2.结合cursor快速理解源码
只研究烟花特效
- 将sound去掉
- 将state去掉
- 只保留canvas元素
分12种烟花效果
arduino
const shellTypes = {
Random: randomShell, //随机 剩余11种里边随机挑选一种
Crackle: crackleShell, // 裂纹
Crossette: crossetteShell,
Crysanthemum: crysanthemumShell, //菊花型
"Falling Leaves": fallingLeavesShell,
Floral: floralShell,
Ghost: ghostShell, //幽灵效果
"Horse Tail": horsetailShell,
Palm: palmShell, //棕榈型
Ring: ringShell, //环形
Strobe: strobeShell, //闪烁效果
Willow: willowShell, //柳树型
}
//因为是白色背景的烟花,换成其他颜色,根据业务需要搭配色值
const COLOR = {
Pink: "#FFEAB2",
Yellow: "#F6FF3D",
Orange: "#FF7E3D",
};
Crackle:
Crossette:
Crysanthemum
Falling Leaves
Floral
Ghost
Horse Tail
Palm
Ring
#### Strobe
#### Willow
烟花绽放效果源码分析
#### Shell - 烟花主体类
kotlin
class Shell {
constructor(options) {
// 将传入的所有选项复制到实例上
Object.assign(this, options);
// 设置星星生命周期变化范围,默认为0.125
this.starLifeVariation = options.starLifeVariation || 0.125;
// 设置烟花颜色,如果没有指定则随机选择
this.color = options.color || randomColor();
// 设置闪光颜色,默认与主体颜色相同
this.glitterColor = options.glitterColor || this.color;
// 如果没有指定星星数量,则根据扩散大小计算
if (!this.starCount) {
const density = options.starDensity || 1;
const scaledSize = this.spreadSize / 54;
this.starCount = Math.max(6, scaledSize * scaledSize * density);
}
}
}
Shell 类支持的主要配置选项包括:
1.
##### 基本参数
```javascript
{
spreadSize: Number, // 爆炸扩散范围大小
starCount: Number, // 星星数量(可选)
starLife: Number, // 星星生命周期
starLifeVariation: Number, // 生命周期变化范围
color: String, // 烟花颜色
glitterColor: String // 闪光颜色
}
```
##### 特效参数
javascript
{
glitter: String, // 闪光效果:'light','medium','heavy','streamer','willow'
pistil: Boolean, // 是否有中心点
pistilColor: String, // 中心点颜色
streamers: Boolean, // 是否有流光效果
crossette: Boolean, // 是否有十字交叉效果
floral: Boolean, // 是否有花朵效果
crackle: Boolean // 是否有爆裂声效果
}
-
主要方法:
ini
// 发射烟花
launch(position, launchHeight) {
const width = stageW;
const height = stageH;
const hpad = 60; // 屏幕边缘保留空间
const vpad = 50; // 顶部保留空间
const minHeightPercent = 0.45;
// 计算发射和爆炸位置
const launchX = position * (width - hpad * 2) + hpad;
const burstY = minHeight - launchHeight * (minHeight - vpad);
// 直接在目标位置爆炸
this.burst(launchX, burstY);
}
// 爆炸效果
burst(x, y) {
// 计算扩散速度
const speed = this.spreadSize / 96;
// 创建爆炸效果
createBurst(this.starCount, (angle, speedMult) => {
// 创建星星粒子
const star = Star.add(
x, y,
this.color,
angle,
speedMult * speed,
this.starLife + Math.random() * this.starLife * this.starLifeVariation
);
// 添加特效
if (this.glitter) {
star.sparkFreq = sparkFreq;
star.sparkSpeed = sparkSpeed;
star.sparkLife = sparkLife;
star.sparkLifeVariation = sparkLifeVariation;
star.sparkColor = this.glitterColor;
star.sparkTimer = Math.random() * star.sparkFreq;
}
});
}
-
特效效果处理
javascript
// 处理双色烟花
if (Array.isArray(this.color)) {
// 创建半圆形双色效果
if (Math.random() < 0.5) {
const start = Math.random() * Math.PI;
const start2 = start + Math.PI;
const arc = Math.PI;
// 分别创建两种颜色的半圆
createBurst(this.starCount, starFactory, start, arc);
createBurst(this.starCount, starFactory, start2, arc);
}
// 创建混合双色效果
else {
createBurst(this.starCount / 2, starFactory);
createBurst(this.starCount / 2, starFactory);
}
}
-
附加效果
kotlin
// 中心点效果
if (this.pistil) {
const innerShell = new Shell({
spreadSize: this.spreadSize * 0.5,
starLife: this.starLife * 0.6,
color: this.pistilColor,
glitter: "light"
});
innerShell.burst(x, y);
}
// 流光效果
if (this.streamers) {
const innerShell = new Shell({
spreadSize: this.spreadSize * 0.9,
starLife: this.starLife * 0.8,
color: COLOR.Pink,
glitter: "streamer"
});
innerShell.burst(x, y);
}
-
Star - 烟花粒子类
ini
const Star = {
// 视觉属性
drawWidth: 3, // 绘制宽度
airDrag: 0.98, // 标准空气阻力
airDragHeavy: 0.992, // 重型粒子空气阻力
// 按颜色存储活动的粒子
active: createParticleCollection(), // 创建粒子集合
_pool: [], // 对象池
// 创建新实例
_new() {
return {};
},
// 添加新粒子
add(x, y, color, angle, speed, life, speedOffX, speedOffY) {
// 从对象池获取或创建新实例
const instance = this._pool.pop() || this._new();
// 基础属性
instance.visible = true; // 是否可见
instance.heavy = false; // 是否重型粒子
instance.x = x; // X坐标
instance.y = y; // Y坐标
instance.prevX = x; // 前一帧X坐标
instance.prevY = y; // 前一帧Y坐标
instance.color = color; // 颜色
// 速度分量
instance.speedX = Math.sin(angle) * speed + (speedOffX || 0);
instance.speedY = Math.cos(angle) * speed + (speedOffY || 0);
// 生命周期
instance.life = life; // 当前生命值
instance.fullLife = life; // 总生命值
// 自旋相关属性
instance.spinAngle = Math.random() * PI_2; // 自旋角度
instance.spinSpeed = 0.8; // 自旋速度
instance.spinRadius = 0; // 自旋半径
// 火花效果属性
instance.sparkFreq = 0; // 火花发射频率
instance.sparkSpeed = 1; // 火花速度
instance.sparkTimer = 0; // 火花计时器
instance.sparkColor = color; // 火花颜色
instance.sparkLife = 750; // 火花生命周期
instance.sparkLifeVariation = 0.25; // 火花生命变化范围
// 特殊效果
instance.strobe = false; // 是否闪烁
instance.alpha = 1; // 透明度
// 将粒子添加到对应颜色的活动集合中
this.active[color].push(instance);
return instance;
},
// 回收粒子实例到对象池
returnInstance(instance) {
// 触发死亡回调
instance.onDeath && instance.onDeath(instance);
// 重置属性
instance.onDeath = null;
instance.secondColor = null;
instance.transitionTime = 0;
instance.colorChanged = false;
// 放回对象池
this._pool.push(instance);
}
};
##### **对象池模式**
javascript
_pool: [],
_new() {
return {};
},
-
使用对象池模式来减少内存分配和垃圾回收
-
复用对象而不是频繁创建新对象
##### **粒子属性系统**
arduino
// 位置和运动
x, y // 当前位置
prevX, prevY // 上一帧位置
speedX, speedY // 速度分量
// 生命周期
life // 当前生命值
fullLife // 总生命值
// 自旋系统
spinAngle // 自旋角度
spinSpeed // 自旋速度
spinRadius // 自旋半径
-
火花效果系统
arduino
// 火花属性
sparkFreq // 发射频率
sparkSpeed // 火花速度
sparkTimer // 计时器
sparkColor // 火花颜色
sparkLife // 生命周期
sparkLifeVariation // 生命变化范围
-
性能优化
kotlin
// 按颜色分组存储
active: createParticleCollection(),
// 使用对象池
const instance = this._pool.pop() || this._new();
-
特效支持
arduino
// 支持多种特效
strobe // 闪烁效果
alpha // 透明度渐变
secondColor // 颜色过渡
transitionTime // 过渡时间
-
使用示例:
csharp
// 创建一个新的星星粒子
const star = Star.add(
100, // x位置
100, // y位置
COLOR.Yellow, // 颜色
Math.PI / 4, // 角度
2, // 速度
1000, // 生命周期
0, // X方向速度偏移
0 // Y方向速度偏移
);
// 添加火花效果
star.sparkFreq = 100; // 每100ms发射一次火花
star.sparkSpeed = 0.5; // 火花速度
star.sparkLife = 500; // 火花持续500ms
// 添加闪烁效果
star.strobe = true;
-
更新机制:
markdown
粒子的更新在主循环中进行,包括:
* 位置更新
* 速度衰减(空气阻力)
* 生命值递减
* 特效处理(火花、闪烁等)
* 自旋运动计算
* 透明度变化
这个类的设计非常精巧,通过组合不同的属性可以创造出各种绚丽的烟花效果。同时通过对象池和颜色分组等优化手段,保证了在大量粒子同时存在的情况下依然能保持良好的性能。
#### Spark - 火花效果类
ini
const Spark = {
// 基础视觉属性
drawWidth: 0, // 绘制宽度
airDrag: 0.9, // 空气阻力系数
// 存储系统
active: createParticleCollection(), // 按颜色分组存储活动的火花
_pool: [], // 对象池
// 创建新实例
_new() {
return {};
},
// 添加新火花
add(x, y, color, angle, speed, life) {
// 从对象池获取或创建新实例
const instance = this._pool.pop() || this._new();
// 设置位置属性
instance.x = x; // 当前X坐标
instance.y = y; // 当前Y坐标
instance.prevX = x; // 前一帧X坐标
instance.prevY = y; // 前一帧Y坐标
// 设置基本属性
instance.color = color; // 火花颜色
// 设置运动属性
instance.speedX = Math.sin(angle) * speed; // X方向速度
instance.speedY = Math.cos(angle) * speed; // Y方向速度
instance.life = life; // 生命周期
instance.alpha = 1; // 透明度
// 将火花添加到对应颜色的活动集合中
this.active[color].push(instance);
return instance;
},
// 回收火花实例到对象池
returnInstance(instance) {
this._pool.push(instance);
}
};
##### **简化设计**
arduino
drawWidth: 0, // 极小的绘制宽度
airDrag: 0.9, // 较大的空气阻力,使火花快速减速
-
对象池管理
javascript
_pool: [],
_new() {
return {};
},
-
相比Star类,Spark类设计更简单
-
火花通常比星星小且生命周期更短
##### **对象池管理**
javascript
_pool: [],
_new() {
return {};
},
-
使用对象池模式优化内存使用
-
减少垃圾回收压力
##### **分组存储**
css
active: createParticleCollection(),
- 按颜色分组存储活动的火花
##### 火花实例属性
javascript
{
// 位置属性
x: Number, // 当前X坐标
y: Number, // 当前Y坐标
prevX: Number, // 上一帧X坐标
prevY: Number, // 上一帧Y坐标
// 运动属性
speedX: Number, // X方向速度
speedY: Number, // Y方向速度
// 视觉属性
color: String, // 颜色
alpha: Number, // 透明度
// 生命周期
life: Number // 剩余生命值
}
-
使用示例
csharp
// 创建一个新的火花
const spark = Spark.add(
100, // x位置
100, // y位置
COLOR.Yellow, // 颜色
Math.random() * PI_2, // 随机角度
1.5, // 速度
300 // 生命周期(300ms)
);
-
更新机制
在主循环中的更新逻辑:
ini
// 火花更新逻辑
COLOR_CODES.forEach(color => {
const sparks = Spark.active[color];
for (let i = sparks.length - 1; i >= 0; i = i - 1) {
const spark = sparks[i];
// 更新生命值
spark.life -= timeStep;
// 检查是否存活
if (spark.life <= 0) {
// 移除死亡的火花
sparks.splice(i, 1);
Spark.returnInstance(spark);
} else {
// 更新位置
spark.prevX = spark.x;
spark.prevY = spark.y;
spark.x += spark.speedX * speed;
spark.y += spark.speedY * speed;
// 应用空气阻力
spark.speedX *= sparkDrag;
spark.speedY *= sparkDrag;
// 应用重力
spark.speedY += gAcc;
}
}
});
-
渲染实现
ini
// 在渲染循环中的绘制逻辑
trailsCtx.lineWidth = Spark.drawWidth;
trailsCtx.lineCap = "butt";
COLOR_CODES.forEach(color => {
const sparks = Spark.active[color];
trailsCtx.strokeStyle = color;
trailsCtx.beginPath();
sparks.forEach(spark => {
// 设置透明度
trailsCtx.globalAlpha = globalAlpha * 0.8;
// 绘制火花轨迹
trailsCtx.moveTo(spark.x, spark.y);
trailsCtx.lineTo(spark.prevX, spark.prevY);
});
trailsCtx.stroke();
});
-
与Star类的主要区别
-
简化属性
- 没有自旋系统
- 没有火花发射系统
- 没有特效系统(如闪烁)
-
物理特性
- 更大的空气阻力(0.9 vs 0.98)
- 更小的绘制宽度(0 vs 3)
- 更短的生命周期
-
视觉效果
- 更细小的粒子
- 更快的消失速度
- 更简单的运动轨迹
Spark类主要用于创建烟花爆炸时的小型火花效果,它们数量多但生命周期短,通过大量这样的小火花可以创造出绚丽的视觉效果。其简化的设计也保证了在大量火花同时存在的情况下,仍能保持良好的性能。
4.
#### BurstFlash - 爆炸闪光效果类
javascript
const BurstFlash = {
// 存储系统
active: [], // 活动的闪光效果数组
_pool: [], // 对象池
// 创建新实例
_new() {
return {};
},
// 添加新的爆炸闪光
add(x, y, radius) {
// 从对象池获取或创建新实例
const instance = this._pool.pop() || this._new();
// 设置基本属性
instance.x = x; // 闪光中心X坐标
instance.y = y; // 闪光中心Y坐标
instance.radius = radius; // 闪光半径
// 将实例添加到活动数组
this.active.push(instance);
return instance;
},
// 回收实例到对象池
returnInstance(instance) {
this._pool.push(instance);
}
};
简单结构
javascript
{
x: Number, // 位置X
y: Number, // 位置Y
radius: Number // 闪光半径
}
- 相比Star和Spark类,结构更简单
- 只需要位置和半径信息
对象池管理
javascript
_pool: [],
_new() {
return {};
}
- 使用对象池模式优化内存使用
- 减少垃圾回收压力
2使用场景
csharp
// 在烟花爆炸时创建闪光效果
BurstFlash.add(
shell.x, // 爆炸位置X
shell.y, // 爆炸位置Y
shell.spreadSize / 4 // 闪光半径为扩散范围的1/4
);
-
渲染实现
arduino
// 在渲染循环中的绘制逻辑
while (BurstFlash.active.length) {
// 获取并移除一个闪光实例
const bf = BurstFlash.active.pop();
// 创建径向渐变
const burstGradient = trailsCtx.createRadialGradient(
bf.x, bf.y, 0, // 内圆心和半径
bf.x, bf.y, bf.radius // 外圆心和半径
);
// 设置渐变颜色停止点
burstGradient.addColorStop(0.024, 'rgba(255, 255, 255, 1)'); // 中心纯白
burstGradient.addColorStop(0.125, 'rgba(255, 160, 20, 0.2)'); // 过渡区域
burstGradient.addColorStop(0.32, 'rgba(255, 140, 20, 0.11)'); // 过渡区域
burstGradient.addColorStop(1, 'rgba(255, 120, 20, 0)'); // 边缘透明
// 应用渐变并绘制
trailsCtx.fillStyle = burstGradient;
trailsCtx.fillRect(
bf.x - bf.radius,
bf.y - bf.radius,
bf.radius * 2,
bf.radius * 2
);
// 回收实例
BurstFlash.returnInstance(bf);
}
-
特点分析
-
即时渲染
- 闪光效果是即时的,不需要动画过渡
- 一旦渲染完成就立即回收实例
-
渐变效果
rust// 渐变颜色设置 0.024 -> 纯白色 (1.0) // 中心最亮 0.125 -> 橙色 (0.2) // 快速衰减 0.32 -> 橙色 (0.11) // 继续衰减 1.0 -> 橙色 (0.0) // 完全透明
- 使用径向渐变创造真实的爆炸光效
- 从中心向外逐渐衰减
性能优化
scss
// 使用矩形绘制而不是圆形
trailsCtx.fillRect(
bf.x - bf.radius,
bf.y - bf.radius,
bf.radius * 2,
bf.radius * 2
);
- 使用fillRect代替arc+fill提高性能
- 径向渐变确保视觉效果是圆形的
应用示例
javascript
// 在Shell类的burst方法中使用
burst(x, y) {
// ... 其他爆炸效果代码 ...
// 创建爆炸闪光
BurstFlash.add(x, y, this.spreadSize / 4);
// ... 其他效果代码 ...
}
-
与其他效果类的关系
-
与Star类的配合
- 在烟花爆炸时提供初始闪光效果
- 增强爆炸瞬间的视觉冲击
-
与Spark类的配合
- 为火花提供初始照明效果
- 增强整体爆炸效果的真实感
-
特点对比
- Star: 持续运动的粒子效果
- Spark: 短暂的火花效果
- BurstFlash: 瞬时的闪光效果
BurstFlash类虽然结构简单,但在整个烟花效果中起到了重要的视觉增强作用。它通过模拟真实烟花爆炸时的瞬间强光,使整个效果更加逼真和震撼。同时,其优化的实现方式也确保了在频繁的烟花爆炸中保持良好的性能。