文章目录
- 代码亮点
- 丰富的烟花效果:
- 粒子系统:
- 性能优化:
- 交互性:
- 博文
- 绚丽烟花:HTML5 Canvas 烟花效果实现
- 代码实现
- HTML 结构:
- 烟花类:
- 粒子类:
- 烟花生成:
- 动画循环:
- 交互性
- 总结
代码亮点
丰富的烟花效果:
- 普通烟花:通过 Firework 类实现,烟花从底部发射到目标位置后爆炸,生成多种形状的粒子。
- 超级烟花:通过 createSuperFirework 函数实现,生成多个层次的烟花效果,增加了视觉的丰富度。
- 天梯烟花:通过 createLadderFirework 函数实现,生成阶梯状的烟花效果,增加了烟花的动态变化。
粒子系统:
- 多种粒子形状:粒子可以是圆形、心形、螺旋形或随机形状,通过 Particle 类的 shape 属性控制。
- 动态效果:粒子的亮度、透明度和速度会随着时间变化,增加了烟花的动态效果。
- 轨迹效果:粒子的轨迹通过 trail 数组记录,绘制时形成连续的线条,增加了视觉的连贯性。
性能优化:
- 粒子数量控制:通过 trailLength 和 decay 属性控制粒子的轨迹长度和衰减速度,避免过多粒子导致性能下降。
- 动态生成:烟花和粒子的生成是动态的,通过 autoLaunchFirework 函数定时生成烟花,避免一次性生成过多烟花。
交互性:
- 点击生成烟花:用户可以通过点击屏幕生成烟花,增加了用户的参与感。
- 随机效果:烟花的生成位置、颜色和形状都是随机的,每次点击都会生成不同的效果。
博文
绚丽烟花:HTML5 Canvas 烟花效果实现
在节日或特殊场合,绚丽的烟花总能带来无尽的欢乐和惊喜。今天,我们将通过 HTML5 Canvas 实现一个绚丽的烟花效果,让你的网页也能绽放出美丽的烟花。
代码实现
HTML 结构:
- 使用一个 canvas 元素作为绘图区域。
- 设置 body 的样式,使 canvas 充满整个屏幕。
<!DOCTYPE html>
<html>
<head>
<title>绚丽烟花</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// JavaScript 代码
</script>
</body>
</html>
烟花类:
Firework 类表示一个烟花,包含烟花的起始位置、目标位置、颜色、大小等属性。
- update 方法更新烟花的位置,判断是否到达目标位置并生成粒子。
- draw 方法绘制烟花的轨迹。
class Firework {
constructor(x, y, targetX, targetY, color, size) {
this.x = x;
this.y = y;
this.startX = x;
this.startY = y;
this.targetX = targetX;
this.targetY = targetY;
this.color = color;
this.size = size;
this.distance = Math.sqrt(Math.pow(targetX - x, 2) + Math.pow(targetY - y, 2));
this.angle = Math.atan2(targetY - y, targetX - x);
this.speed = 2;
this.acceleration = 1.05;
this.brightness = random(50, 80);
this.targetRadius = 1;
this.trail = [];
this.trailLength = 10;
}
update() {
this.trail.push([this.x, this.y]);
if (this.trail.length > this.trailLength) {
this.trail.shift();
}
this.speed *= this.acceleration;
let vx = Math.cos(this.angle) * this.speed;
let vy = Math.sin(this.angle) * this.speed;
this.distanceTraveled = Math.sqrt(Math.pow(this.x - this.startX, 2) + Math.pow(this.y - this.startY, 2));
if (this.distanceTraveled >= this.distance) {
createParticles(this.targetX, this.targetY, this.color, this.size);
return true;
}
this.x += vx;
this.y += vy;
return false;
}
draw() {
ctx.beginPath();
ctx.moveTo(this.trail[0][0], this.trail[0][1]);
for(let i = 1; i < this.trail.length; i++) {
ctx.lineTo(this.trail[i][0], this.trail[i][1]);
}
ctx.strokeStyle = `hsla(${this.color}, 100%, ${this.brightness}%, ${this.brightness/100})`;
ctx.lineWidth = this.targetRadius * 2;
ctx.stroke();
}
}
粒子类:
Particle 类表示一个粒子,包含粒子的位置、颜色、大小、形状等属性。
- update 方法更新粒子的位置和状态,判断是否消失。
- draw 方法绘制粒子的轨迹。
class Particle {
constructor(x, y, hue, size, angle, speed, shape) {
this.x = x;
this.y = y;
this.hue = hue;
this.size = size;
this.angle = angle;
this.speed = speed;
this.shape = shape;
this.brightness = random(50, 80);
this.alpha = 1;
this.decay = random(0.015, 0.03);
this.gravity = 0.7;
this.trail = [];
this.trailLength = 5;
this.twinkle = Math.random() > 0.7;
this.twinkleSpeed = random(0.03, 0.07);
this.twinkleTime = 0;
switch(this.shape) {
case 'circle':
this.velocity = {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
};
break;
case 'heart':
const heartShape = this.heartShape(angle);
this.velocity = {
x: heartShape.x * speed,
y: heartShape.y * speed
};
break;
case 'spiral':
this.spiralAngle = angle;
this.radius = speed * 2;
this.velocity = {x: 0, y: 0};
break;
case 'ladder':
this.velocity = {
x: Math.cos(angle) * speed * 0.5,
y: Math.sin(angle) * speed * 0.5
};
break;
default:
this.velocity = {
x: random(-15, 15) * size,
y: random(-15, 15) * size
};
}
}
heartShape(angle) {
const t = angle;
const x = 16 * Math.pow(Math.sin(t), 3);
const y = 13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t);
return {x: x/16, y: -y/16};
}
update() {
this.trail.push([this.x, this.y]);
if (this.trail.length > this.trailLength) {
this.trail.shift();
}
if (this.shape === 'spiral') {
this.spiralAngle += 0.1;
this.radius *= 0.99;
this.x += Math.cos(this.spiralAngle) * this.radius;
this.y += Math.sin(this.spiralAngle) * this.radius;
} else {
this.velocity.x *= 0.99;
this.velocity.y *= 0.99;
this.velocity.y += this.gravity;
this.x += this.velocity.x;
this.y += this.velocity.y;
}
if (this.twinkle) {
this.twinkleTime += this.twinkleSpeed;
this.brightness = 50 + Math.sin(this.twinkleTime) * 30;
}
this.alpha -= this.decay;
return this.alpha <= 0;
}
draw() {
ctx.beginPath();
ctx.moveTo(this.trail[0][0], this.trail[0][1]);
for(let i = 1; i < this.trail.length; i++) {
ctx.lineTo(this.trail[i][0], this.trail[i][1]);
}
if (this.size > 1.5) {
ctx.shadowBlur = 15;
ctx.shadowColor = `hsla(${this.hue}, 100%, 50%, ${this.alpha})`;
} else {
ctx.shadowBlur = 0;
}
ctx.strokeStyle = `hsla(${this.hue}, 100%, ${this.brightness}%, ${this.alpha})`;
ctx.lineWidth = 2 * this.size;
ctx.stroke();
}
}
烟花生成:
- createParticles 函数生成烟花爆炸后的粒子。
- createSuperFirework 函数生成超级烟花效果。
- createLadderFirework 函数生成天梯烟花效果。
function createParticles(x, y, hue, size) {
const shapes = ['circle', 'heart', 'spiral', 'random'];
const selectedShape = shapes[Math.floor(random(0, shapes.length))];
let particleCount = Math.floor(random(80, 150) * size);
for (let i = 0; i < particleCount; i++) {
const angle = (Math.PI * 2 * i) / particleCount;
const speed = random(5, 10) * size;
particles.push(new Particle(x, y, hue, size, angle, speed, selectedShape));
}
}
function createSuperFirework(x, y) {
const mainSize = random(2, 3);
const mainHue = random(0, 360);
createParticles(x, y, mainHue, mainSize);
setTimeout(() => {
const particleCount = 120;
for (let i = 0; i < particleCount; i++) {
const angle = (Math.PI * 2 * i) / particleCount;
const speed = random(8, 12);
const hue = (mainHue + random(-20, 20)) % 360;
particles.push(new Particle(x, y, hue, mainSize * 0.8, angle, speed, 'circle'));
}
}, 100);
for (let i = 0; i < 5; i++) {
setTimeout(() => {
const offset = 50;
const newX = x + random(-offset, offset);
const newY = y + random(-offset, offset);
const hue = (mainHue + 72 * i) % 360;
createParticles(newX, newY, hue, mainSize * 0.6);
}, random(200, 400));
}
}
function createLadderFirework(startX, startY) {
const steps = 10; // 梯子的阶数
const stepHeight = 50; // 每阶高度
const stepWidth = 30; // 每阶宽度
const baseHue = random(0, 360);
function createStep(index) {
if (index >= steps) return;
const x = startX + (index % 2 === 0 ? stepWidth : -stepWidth);
const y = startY - (index * stepHeight);
const hue = (baseHue + index * 15) % 360;
const size = 1 - (index * 0.05);
// 创建阶梯形状的粒子
const particleCount = 30;
for (let i = 0; i < particleCount; i++) {
const angle = (Math.PI * 2 * i) / particleCount;
const speed = random(2, 4);
particles.push(new Particle(x, y, hue, size, angle, speed, 'ladder'));
}
// 添加连接线效果
if (index > 0) {
const lineParticles = 15;
const prevX = startX + ((index-1) % 2 === 0 ? stepWidth : -stepWidth);
const prevY = startY - ((index-1) * stepHeight);
for (let i = 0; i < lineParticles; i++) {
const progress = i / lineParticles;
const lineX = prevX + (x - prevX) * progress;
const lineY = prevY + (y - prevY) * progress;
particles.push(new Particle(
lineX,
lineY,
hue,
size * 0.8,
Math.PI / 2,
2,
'ladder'
));
}
}
// 延迟创建下一阶
setTimeout(() => createStep(index + 1), 100);
}
createStep(0);
}
动画循环:
animate 函数负责绘制烟花和粒子,更新它们的状态,并清除旧的轨迹。
function animate() {
requestAnimationFrame(animate);
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (random(0, 1) > 0.95) {
autoLaunchFirework();
}
for (let i = fireworks.length - 1; i >= 0; i--) {
if (fireworks[i].update()) {
fireworks.splice(i, 1);
} else {
fireworks[i].draw();
}
}
for (let i = particles.length - 1; i >= 0; i--) {
if (particles[i].update()) {
particles.splice(i, 1);
} else {
particles[i].draw();
}
}
}
animate();
交互性
用户可以通过点击屏幕生成烟花,增加了用户的参与感。点击屏幕时,会随机生成普通烟花或天梯烟花。
canvas.addEventListener('click', (e) => {
const x = e.clientX;
const y = e.clientY;
if (Math.random() > 0.5) {
createSuperFirework(x, y);
} else {
createLadderFirework(x, canvas.height - 100); // 从底部开始生成天梯
}
});
总结
通过 HTML5 Canvas 和 JavaScript,我们实现了一个绚丽的烟花效果。代码中包含了多种烟花效果,如普通烟花、超级烟花和天梯烟花,增加了视觉的丰富度。粒子系统的设计使得烟花的动态效果更加逼真,性能优化确保了动画的流畅性。用户可以通过点击屏幕生成烟花,增加了用户的参与感。希望这个代码能给你带来灵感,让你的网页也能绽放出美丽的烟花。
完整代码如下:
<!DOCTYPE html>
<html>
<head>
<title>绚丽烟花</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// 烟花类
class Firework {
constructor(x, y, targetX, targetY, color, size) {
this.x = x;
this.y = y;
this.startX = x;
this.startY = y;
this.targetX = targetX;
this.targetY = targetY;
this.color = color;
this.size = size;
this.distance = Math.sqrt(Math.pow(targetX - x, 2) + Math.pow(targetY - y, 2));
this.angle = Math.atan2(targetY - y, targetX - x);
this.speed = 2;
this.acceleration = 1.05;
this.brightness = random(50, 80);
this.targetRadius = 1;
this.trail = [];
this.trailLength = 10;
}
update() {
this.trail.push([this.x, this.y]);
if (this.trail.length > this.trailLength) {
this.trail.shift();
}
this.speed *= this.acceleration;
let vx = Math.cos(this.angle) * this.speed;
let vy = Math.sin(this.angle) * this.speed;
this.distanceTraveled = Math.sqrt(Math.pow(this.x - this.startX, 2) + Math.pow(this.y - this.startY, 2));
if (this.distanceTraveled >= this.distance) {
createParticles(this.targetX, this.targetY, this.color, this.size);
return true;
}
this.x += vx;
this.y += vy;
return false;
}
draw() {
ctx.beginPath();
ctx.moveTo(this.trail[0][0], this.trail[0][1]);
for(let i = 1; i < this.trail.length; i++) {
ctx.lineTo(this.trail[i][0], this.trail[i][1]);
}
ctx.strokeStyle = `hsla(${this.color}, 100%, ${this.brightness}%, ${this.brightness/100})`;
ctx.lineWidth = this.targetRadius * 2;
ctx.stroke();
}
}
// 粒子类
class Particle {
constructor(x, y, hue, size, angle, speed, shape) {
this.x = x;
this.y = y;
this.hue = hue;
this.size = size;
this.angle = angle;
this.speed = speed;
this.shape = shape;
this.brightness = random(50, 80);
this.alpha = 1;
this.decay = random(0.015, 0.03);
this.gravity = 0.7;
this.trail = [];
this.trailLength = 5;
this.twinkle = Math.random() > 0.7;
this.twinkleSpeed = random(0.03, 0.07);
this.twinkleTime = 0;
switch(this.shape) {
case 'circle':
this.velocity = {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
};
break;
case 'heart':
const heartShape = this.heartShape(angle);
this.velocity = {
x: heartShape.x * speed,
y: heartShape.y * speed
};
break;
case 'spiral':
this.spiralAngle = angle;
this.radius = speed * 2;
this.velocity = {x: 0, y: 0};
break;
case 'ladder':
this.velocity = {
x: Math.cos(angle) * speed * 0.5,
y: Math.sin(angle) * speed * 0.5
};
break;
default:
this.velocity = {
x: random(-15, 15) * size,
y: random(-15, 15) * size
};
}
}
heartShape(angle) {
const t = angle;
const x = 16 * Math.pow(Math.sin(t), 3);
const y = 13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t);
return {x: x/16, y: -y/16};
}
update() {
this.trail.push([this.x, this.y]);
if (this.trail.length > this.trailLength) {
this.trail.shift();
}
if (this.shape === 'spiral') {
this.spiralAngle += 0.1;
this.radius *= 0.99;
this.x += Math.cos(this.spiralAngle) * this.radius;
this.y += Math.sin(this.spiralAngle) * this.radius;
} else {
this.velocity.x *= 0.99;
this.velocity.y *= 0.99;
this.velocity.y += this.gravity;
this.x += this.velocity.x;
this.y += this.velocity.y;
}
if (this.twinkle) {
this.twinkleTime += this.twinkleSpeed;
this.brightness = 50 + Math.sin(this.twinkleTime) * 30;
}
this.alpha -= this.decay;
return this.alpha <= 0;
}
draw() {
ctx.beginPath();
ctx.moveTo(this.trail[0][0], this.trail[0][1]);
for(let i = 1; i < this.trail.length; i++) {
ctx.lineTo(this.trail[i][0], this.trail[i][1]);
}
if (this.size > 1.5) {
ctx.shadowBlur = 15;
ctx.shadowColor = `hsla(${this.hue}, 100%, 50%, ${this.alpha})`;
} else {
ctx.shadowBlur = 0;
}
ctx.strokeStyle = `hsla(${this.hue}, 100%, ${this.brightness}%, ${this.alpha})`;
ctx.lineWidth = 2 * this.size;
ctx.stroke();
}
}
let fireworks = [];
let particles = [];
function random(min, max) {
return Math.random() * (max - min) + min;
}
function createLadderFirework(startX, startY) {
const steps = 10; // 梯子的阶数
const stepHeight = 50; // 每阶高度
const stepWidth = 30; // 每阶宽度
const baseHue = random(0, 360);
function createStep(index) {
if (index >= steps) return;
const x = startX + (index % 2 === 0 ? stepWidth : -stepWidth);
const y = startY - (index * stepHeight);
const hue = (baseHue + index * 15) % 360;
const size = 1 - (index * 0.05);
// 创建阶梯形状的粒子
const particleCount = 30;
for (let i = 0; i < particleCount; i++) {
const angle = (Math.PI * 2 * i) / particleCount;
const speed = random(2, 4);
particles.push(new Particle(x, y, hue, size, angle, speed, 'ladder'));
}
// 添加连接线效果
if (index > 0) {
const lineParticles = 15;
const prevX = startX + ((index-1) % 2 === 0 ? stepWidth : -stepWidth);
const prevY = startY - ((index-1) * stepHeight);
for (let i = 0; i < lineParticles; i++) {
const progress = i / lineParticles;
const lineX = prevX + (x - prevX) * progress;
const lineY = prevY + (y - prevY) * progress;
particles.push(new Particle(
lineX,
lineY,
hue,
size * 0.8,
Math.PI / 2,
2,
'ladder'
));
}
}
// 延迟创建下一阶
setTimeout(() => createStep(index + 1), 100);
}
createStep(0);
}
function createParticles(x, y, hue, size) {
const shapes = ['circle', 'heart', 'spiral', 'random'];
const selectedShape = shapes[Math.floor(random(0, shapes.length))];
let particleCount = Math.floor(random(80, 150) * size);
for (let i = 0; i < particleCount; i++) {
const angle = (Math.PI * 2 * i) / particleCount;
const speed = random(5, 10) * size;
particles.push(new Particle(x, y, hue, size, angle, speed, selectedShape));
}
}
function createSuperFirework(x, y) {
const mainSize = random(2, 3);
const mainHue = random(0, 360);
createParticles(x, y, mainHue, mainSize);
setTimeout(() => {
const particleCount = 120;
for (let i = 0; i < particleCount; i++) {
const angle = (Math.PI * 2 * i) / particleCount;
const speed = random(8, 12);
const hue = (mainHue + random(-20, 20)) % 360;
particles.push(new Particle(x, y, hue, mainSize * 0.8, angle, speed, 'circle'));
}
}, 100);
for (let i = 0; i < 5; i++) {
setTimeout(() => {
const offset = 50;
const newX = x + random(-offset, offset);
const newY = y + random(-offset, offset);
const hue = (mainHue + 72 * i) % 360;
createParticles(newX, newY, hue, mainSize * 0.6);
}, random(200, 400));
}
}
canvas.addEventListener('click', (e) => {
const x = e.clientX;
const y = e.clientY;
if (Math.random() > 0.5) {
createSuperFirework(x, y);
} else {
createLadderFirework(x, canvas.height - 100); // 从底部开始生成天梯
}
});
function autoLaunchFirework() {
if (Math.random() > 0.8) { // 20%的概率生成天梯
let startX = random(canvas.width * 0.2, canvas.width * 0.8);
createLadderFirework(startX, canvas.height - 100);
} else {
// 原有的普通烟花代码
let startX = random(canvas.width * 0.2, canvas.width * 0.8);
let startY = canvas.height;
let endX = random(canvas.width * 0.1, canvas.width * 0.9);
let endY = random(canvas.height * 0.2, canvas.height * 0.5);
let hue = random(0, 360);
let size = random(0.5, 1.8);
fireworks.push(new Firework(startX, startY, endX, endY, hue, size));
}
}
function animate() {
requestAnimationFrame(animate);
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (random(0, 1) > 0.95) {
autoLaunchFirework();
}
for (let i = fireworks.length - 1; i >= 0; i--) {
if (fireworks[i].update()) {
fireworks.splice(i, 1);
} else {
fireworks[i].draw();
}
}
for (let i = particles.length - 1; i >= 0; i--) {
if (particles[i].update()) {
particles.splice(i, 1);
} else {
particles[i].draw();
}
}
}
animate();
</script>
</body>
</html>