canvas炸烟花思路总结
canvas炸烟花思路总结
效果

源码
html
点击查看代码
复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>烟花效果</title>
<style>
canvas {
margin: 80px auto;
display: block;
box-shadow: 0 0 10px rgb(0 0 0 / 50%);
}
</style>
</head>
<body onload="draw()">
<canvas width="800" height="500"></canvas>
<script src="./js/烟花.js"></script>
<script src="./js/烟花粒子.js"></script>
<script>
let timeTicker = 0
// 动画执行80次,发射8个烟花
let timeTotal = 80
let fireworks = []
// 一个烟花有多少个粒子
const particlesCount = 50
let particles = []
function draw() {
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// 透明度控制拖尾效果
ctx.fillStyle = 'rgba(0,0,0,0.03)'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// 画烟花
for (let index = 0; index < fireworks.length; index++) {
const firework = fireworks[index]
firework.draw(ctx)
if (firework.isArrived()) {
fireworks.splice(index, 1)
index--
for (let j = 0; j < particlesCount; j++) {
particles.push(
new Particle(
...firework.getTargetCoordinate(),
firework.getHue()
)
)
}
}
}
// 画粒子
for (let index = 0; index < particles.length; index++) {
const particle = particles[index]
particle.draw(ctx)
if (particle.isVanished()) {
particles.splice(index, 1)
index--
}
}
// 函数循环80次自动发射8支烟花
if (timeTicker >= timeTotal) {
for (let index = 0; index < timeTotal / 10; index++) {
fireworks.push(
new FireWork(
canvas.width / 2,
canvas.height,
getRandomRange(0, canvas.width),
getRandomRange(100, canvas.height / 2)
)
)
}
timeTicker = 0
} else {
timeTicker++
}
requestAnimationFrame(draw)
}
function getRandomRange(min, max) {
return Math.random() * (max - min) + min
}
</script>
</body>
</html>
烟花.js
点击查看代码
class FireWork {
constructor(startX, startY, endX, endY) {
// 烟花起始点(从哪个地方开始发射)
this.startX = startX
this.startY = startY
// 当前坐标(烟花移动过程中使用)
this.x = this.startX
this.y = this.startY
// 烟花结束点(发射到哪个地方)
this.endX = endX
this.endY = endY
// 目标距离
this.targetDistance = this._getPointsDistance(
this.startX,
this.startY,
this.endX,
this.endY
)
// 当前烟花移动的距离
this.currentDistance = 0
// hsla 色调
this.hue = this._getRandomRange(0, 360)
// hsla 亮度
this.lightness = this._getRandomRange(50, 70)
// 被发射的角度(根据给定的开始、结束位置计算角度)
this.angle = Math.atan2(this.endY - this.startY, this.endX - this.startX)
// 移动速度
this.speed = 2
this.acceleration = 1.05 // 加速度系数
}
draw(ctx) {
ctx.save()
this.speed *= this.acceleration
const moveX = this.x + Math.cos(this.angle) * this.speed
const moveY = this.y + Math.sin(this.angle) * this.speed
ctx.strokeStyle = `hsla(${this.hue}, 100%, ${this.lightness}%, 60%)` // 仅亮度会变化
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(this.x, this.y)
ctx.lineTo(moveX, moveY)
ctx.stroke()
ctx.restore()
this.currentDistance = this._getPointsDistance(
this.startX,
this.startY,
moveX,
moveY
)
this.x = moveX
this.y = moveY
}
// 是否到达目标地点
isArrived() {
return this.currentDistance >= this.targetDistance
}
// 获取烟花色调
getHue() {
return this.hue
}
// 获取目标位置的坐标
getTargetCoordinate() {
return [this.endX, this.endY]
}
_getRandomRange(min, max) {
return Math.random() * (max - min) + min
}
// 计算直角(笛卡尔)坐标系中两点间距离
_getPointsDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
}
}
烟花粒子.js
点击查看代码
class Particle {
constructor(x, y, hue) {
// 初始位置
this.x = x
this.y = y
// 从烟花那得来的hsla色调
this.hue = this._getRandomRange(hue - 20, hue + 20)
//this.hue = hue
// 随机hsla亮度
this.lightness = this._getRandomRange(50, 80)
// 初始透明度
this.alpha = 1
// 随机的透明度衰变系数(透明度减淡)
this.alphaDecay = this._getRandomRange(0.015, 0.03)
// 模拟下坠摩擦力(让粒子减速)
this.friction = 0.95
// 模拟重力加速度
this.gravity = 1
// 随机角度
this.angle = this._getRandomRange(0, 2 * Math.PI)
// 随机初始速度
this.speed = this._getRandomRange(1, 10)
}
draw(ctx) {
ctx.save()
// 减速
this.speed *= this.friction
// 计算新位置
const moveX = this.x + Math.cos(this.angle) * this.speed
const moveY = this.y + Math.sin(this.angle) * this.speed + this.gravity
ctx.beginPath()
ctx.moveTo(this.x, this.y)
ctx.lineTo(moveX, moveY)
ctx.strokeStyle = `hsla(${this.hue}, 100%, ${this.lightness}%, ${this.alpha})`
ctx.lineWidth = 3
ctx.lineCap = 'round'
ctx.stroke()
ctx.restore()
this.x = moveX
this.y = moveY
// 透明度衰减
this.alpha -= this.alphaDecay
}
_getRandomRange(min, max) {
return Math.random() * (max - min) + min
}
// 粒子是否已经看不见
isVanished() {
return this.alpha < this.alphaDecay
}
}
主要流程
烟花被发射到指定位置后消失,粒子绽放,实现烟花绽放的效果
烟花发射
- 烟花射速控制,动画执行80次后,发射8个烟花(控制发射频率)
- 烟花发射前,其角度、颜色、目标位置均已确定
- 角度、颜色、目标位置都是随机的
- 烟花发射时有初始速度和加速度(表现在烟花消失时刻的那个冲劲儿)
- 烟花到达目标位置后消失,绽放粒子
粒子绽放
- 每个粒子绽放时,初始位置、速度、角度、颜色均已确定
- 初始位置:烟花的结束位置
- 速度:初始是随机的。移动过程中模拟摩擦力/阻力,速度逐渐减小
- 角度:随机。生成后不再发生变化
- 颜色:烟花的颜色。可以围绕烟花的颜色有些许变化,呈现五颜六色的效果
- 为模拟物体下坠,粒子移动过程中需要有模拟的摩擦力(粒子移速减小)、重力加速度(模拟粒子有重力)
- 粒子移动过程中,颜色逐渐透明(表示粒子/烟花消失)。低于一定的透明度时,粒子消失
路线形成
烟花和粒子的移动路线计算都符合下面的流程
- 初始位置随机
- 移动过程中,随着速度发生变化,根据角度计算移动的坐标
- 用上一次的结束坐标作为这一次的开始坐标,并重新根据速度、角度(因为要沿着同一个方向走)计算要移动的坐标距离(坐标变化量)
- 新坐标=旧坐标+坐标变化量
拖尾效果
fillStyle
的透明度控制拖尾效果,越透明,拖尾越明显
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义