绚丽烟花:HTML5 Canvas 烟花效果实现(文末附完整代码)

在这里插入图片描述

文章目录

    • 代码亮点
      • 丰富的烟花效果:
      • 粒子系统:
      • 性能优化:
      • 交互性:
    • 博文
      • 绚丽烟花: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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值