新年来临之前,业务需求要求整一个烟花特效,对canvas研究不多,翻遍GitHub,找到了一个合眼缘的烟花效果,拿到源码了咱们来根据需求进行特制改造。 此库的效果展示,甚合我心,在情人节前夕,分享给各位有心人。
1.结合cursor达成业务需求
最终效果
2.结合cursor快速理解源码
只研究烟花特效
- 将sound去掉
- 将state去掉
- 只保留canvas元素
-
分12种烟花效果
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 - 烟花主体类
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 类支持的主要配置选项包括:
-
基本参数
{ spreadSize: Number, // 爆炸扩散范围大小 starCount: Number, // 星星数量(可选) starLife: Number, // 星星生命周期 starLifeVariation: Number, // 生命周期变化范围 color: String, // 烟花颜色 glitterColor: String // 闪光颜色 }
-
特效参数
{ glitter: String, // 闪光效果:'light','medium','heavy','streamer','willow' pistil: Boolean, // 是否有中心点 pistilColor: String, // 中心点颜色 streamers: Boolean, // 是否有流光效果 crossette: Boolean, // 是否有十字交叉效果 floral: Boolean, // 是否有花朵效果 crackle: Boolean // 是否有爆裂声效果 }
3. ##### 主要方法:
// 发射烟花 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; } }); }
4. ##### 特效效果处理
// 处理双色烟花 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); } }
5. ##### 附加效果
// 中心点效果 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); }
2. #### Star - 烟花粒子类
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); } };
-
对象池模式
_pool: [], _new() { return {}; },
-
使用对象池模式来减少内存分配和垃圾回收
-
复用对象而不是频繁创建新对象
-
粒子属性系统
// 位置和运动 x, y // 当前位置 prevX, prevY // 上一帧位置 speedX, speedY // 速度分量 // 生命周期 life // 当前生命值 fullLife // 总生命值 // 自旋系统 spinAngle // 自旋角度 spinSpeed // 自旋速度 spinRadius // 自旋半径
3. ##### 火花效果系统
// 火花属性 sparkFreq // 发射频率 sparkSpeed // 火花速度 sparkTimer // 计时器 sparkColor // 火花颜色 sparkLife // 生命周期 sparkLifeVariation // 生命变化范围
4. ##### 性能优化
// 按颜色分组存储 active: createParticleCollection(), // 使用对象池 const instance = this._pool.pop() || this._new();
5. ##### 特效支持
// 支持多种特效 strobe // 闪烁效果 alpha // 透明度渐变 secondColor // 颜色过渡 transitionTime // 过渡时间
6. ##### 使用示例:
// 创建一个新的星星粒子 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;
7. ##### 更新机制:
粒子的更新在主循环中进行,包括: * 位置更新 * 速度衰减(空气阻力) * 生命值递减 * 特效处理(火花、闪烁等) * 自旋运动计算 * 透明度变化 这个类的设计非常精巧,通过组合不同的属性可以创造出各种绚丽的烟花效果。同时通过对象池和颜色分组等优化手段,保证了在大量粒子同时存在的情况下依然能保持良好的性能。
-
Spark - 火花效果类
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); } };
-
简化设计
drawWidth: 0, // 极小的绘制宽度 airDrag: 0.9, // 较大的空气阻力,使火花快速减速
2. ##### 对象池管理
_pool: [], _new() { return {}; },
-
相比Star类,Spark类设计更简单
-
火花通常比星星小且生命周期更短
-
对象池管理
_pool: [], _new() { return {}; },
-
使用对象池模式优化内存使用
-
减少垃圾回收压力
-
分组存储
active: createParticleCollection(),
-
按颜色分组存储活动的火花
-
火花实例属性
{ // 位置属性 x: Number, // 当前X坐标 y: Number, // 当前Y坐标 prevX: Number, // 上一帧X坐标 prevY: Number, // 上一帧Y坐标 // 运动属性 speedX: Number, // X方向速度 speedY: Number, // Y方向速度 // 视觉属性 color: String, // 颜色 alpha: Number, // 透明度 // 生命周期 life: Number // 剩余生命值 }
6. ##### 使用示例
// 创建一个新的火花 const spark = Spark.add( 100, // x位置 100, // y位置 COLOR.Yellow, // 颜色 Math.random() * PI_2, // 随机角度 1.5, // 速度 300 // 生命周期(300ms) );
7. ##### 更新机制
在主循环中的更新逻辑:
// 火花更新逻辑 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; } } });
8. ##### 渲染实现
// 在渲染循环中的绘制逻辑 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(); });
9. ##### 与Star类的主要区别
-
简化属性
- 没有自旋系统
- 没有火花发射系统
- 没有特效系统(如闪烁)
-
物理特性
- 更大的空气阻力(0.9 vs 0.98)
- 更小的绘制宽度(0 vs 3)
- 更短的生命周期
-
视觉效果
- 更细小的粒子
- 更快的消失速度
- 更简单的运动轨迹
Spark类主要用于创建烟花爆炸时的小型火花效果,它们数量多但生命周期短,通过大量这样的小火花可以创造出绚丽的视觉效果。其简化的设计也保证了在大量火花同时存在的情况下,仍能保持良好的性能。
-
BurstFlash - 爆炸闪光效果类
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); } };
-
-
简单结构
{
x: Number, // 位置X
y: Number, // 位置Y
radius: Number // 闪光半径
}
- 相比Star和Spark类,结构更简单
- 只需要位置和半径信息
-
对象池管理
_pool: [],
_new() {
return {};
}
- 使用对象池模式优化内存使用
- 减少垃圾回收压力
-
2使用场景
// 在烟花爆炸时创建闪光效果
BurstFlash.add(
shell.x, // 爆炸位置X
shell.y, // 爆炸位置Y
shell.spreadSize / 4 // 闪光半径为扩散范围的1/4
);
4. ##### 渲染实现
// 在渲染循环中的绘制逻辑
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);
}
5. ##### 特点分析
-
即时渲染
- 闪光效果是即时的,不需要动画过渡
- 一旦渲染完成就立即回收实例
-
渐变效果
-
// 渐变颜色设置 0.024 -> 纯白色 (1.0) // 中心最亮 0.125 -> 橙色 (0.2) // 快速衰减 0.32 -> 橙色 (0.11) // 继续衰减 1.0 -> 橙色 (0.0) // 完全透明
- 使用径向渐变创造真实的爆炸光效
- 从中心向外逐渐衰减
-
-
性能优化
// 使用矩形绘制而不是圆形
trailsCtx.fillRect(
bf.x - bf.radius,
bf.y - bf.radius,
bf.radius * 2,
bf.radius * 2
);
- 使用fillRect代替arc+fill提高性能
- 径向渐变确保视觉效果是圆形的
-
应用示例
// 在Shell类的burst方法中使用
burst(x, y) {
// ... 其他爆炸效果代码 ...
// 创建爆炸闪光
BurstFlash.add(x, y, this.spreadSize / 4);
// ... 其他效果代码 ...
}
8. ##### 与其他效果类的关系
-
与Star类的配合
- 在烟花爆炸时提供初始闪光效果
- 增强爆炸瞬间的视觉冲击
-
与Spark类的配合
- 为火花提供初始照明效果
- 增强整体爆炸效果的真实感
-
特点对比
- Star: 持续运动的粒子效果
- Spark: 短暂的火花效果
- BurstFlash: 瞬时的闪光效果
BurstFlash类虽然结构简单,但在整个烟花效果中起到了重要的视觉增强作用。它通过模拟真实烟花爆炸时的瞬间强光,使整个效果更加逼真和震撼。同时,其优化的实现方式也确保了在频繁的烟花爆炸中保持良好的性能。