当代码遇上烟花:JS 和 Canvas 创造的梦幻之境

392 阅读4分钟

这段代码创建了一个具有动态烟花效果的页面,通过 JavaScript 和 canvas 技术实现了烟花的发射和爆炸效果,为页面添加了视觉吸引力和互动性。


大家复制代码时,可能会因格式转换出现错乱,导致样式失效。建议先少量复制代码进行测试,若未能解决问题,私信我,我会发送完整的压缩包给你

演示效果

HTML&CSS

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap"
        rel="stylesheet">
    <title>公众号关注:前端Hardy</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif;
        }

        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: #222;
            overflow: hidden;
        }

        :root {
            --title: "Click Crazy Fireworks";
            --author: "Matt Cannon";
            --contact: "mc@mattcannon.design";
            --description: "Random fireworks over a blank void, as is life. Click like crazy to see the finale!";
            --keywords: "codepenchallenge, cpc-celebration, fireworks, animation, canvas, JavaScript, visual effects, celebration, night sky, particles, interactive, generative art, dynamic display, colorful explosions, seamless animations";
            --last-modified: "2024-12-16";
            --content-language: "en";
            --generator: "HTML5, CSS3, JavaScript";
        }

        canvas {
            display: block;
        }
    </style>
</head>

<body>
    <script>
        window.addEventListener("load", function () {
            const canv = document.createElement("canvas");
            canv.style.position = "absolute";
            canv.style.top = "0";
            canv.style.left = "0";
            canv.style.width = "100%";
            canv.style.height = "100%";
            document.body.appendChild(canv);
            const ctx = canv.getContext("2d");
            let maxx = window.innerWidth;
            let maxy = window.innerHeight;
            canv.width = maxx;
            canv.height = maxy;
            window.addEventListener("resize", () => {
                maxx = window.innerWidth;
                maxy = window.innerHeight;
                canv.width = maxx;
                canv.height = maxy;
            });

            const rand = (min, max) => Math.random() * (max - min) + min;
            const randInt = (min, max) => Math.floor(Math.random() * (max - min) + min);
            const randColor = () => `hsl(${randInt(0, 360)}, 100%, 50%)`;

            class Particle {
                constructor(x, y, color, speed, direction, gravity, friction, size) {
                    this.x = x;
                    this.y = y;
                    this.color = color;
                    this.speed = speed;
                    this.direction = direction;
                    this.vx = Math.cos(direction) * speed;
                    this.vy = Math.sin(direction) * speed;
                    this.gravity = gravity;
                    this.friction = friction;
                    this.alpha = 1;
                    this.decay = rand(0.005, 0.02);
                    this.size = size;
                }
                update() {
                    this.vx *= this.friction;
                    this.vy *= this.friction;
                    this.vy += this.gravity;
                    this.x += this.vx;
                    this.y += this.vy;
                    this.alpha -= this.decay;
                }

                draw(ctx) {
                    ctx.save();
                    ctx.globalAlpha = this.alpha;
                    ctx.beginPath();
                    ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
                    ctx.fillStyle = this.color;
                    ctx.fill();
                    ctx.restore();
                }
                isAlive() {
                    return this.alpha > 0;
                }
            }
            class Firework {
                constructor(x, y, targetY, color, speed, size) {
                    this.x = x;
                    this.y = y;
                    this.targetY = targetY;
                    this.color = color;
                    this.speed = speed;
                    this.size = size;
                    this.angle = -Math.PI / 2 + rand(-0.3, 0.3);
                    this.vx = Math.cos(this.angle) * this.speed;
                    this.vy = Math.sin(this.angle) * this.speed;
                    this.trail = [];
                    this.trailLength = randInt(10, 25);
                    this.exploded = false;
                }
                update() {
                    this.trail.push({ x: this.x, y: this.y });
                    if (this.trail.length > this.trailLength) {
                        this.trail.shift();
                    }

                    this.x += this.vx;
                    this.y += this.vy;
                    this.vy += 0.02;

                    if (this.vy >= 0 || this.y <= this.targetY) {
                        this.explode();
                        return false;
                    }
                    return true;
                }
                explode() {
                    const numParticles = randInt(50, 150);
                    for (let i = 0; i < numParticles; i++) {
                        const angle = rand(0, Math.PI * 2);
                        const speed = rand(2, 7);
                        const particleSize = rand(1, 5);
                        explosions.push(
                            new Particle(
                                this.x,
                                this.y,
                                this.color,
                                speed,
                                angle,
                                0.05,
                                0.98,
                                particleSize
                            )
                        );
                    }
                }


                draw(ctx) {
                    ctx.save();
                    ctx.beginPath();
                    if (this.trail.length > 1) {
                        ctx.moveTo(this.trail[0].x, this.trail[0].y);
                        for (let point of this.trail) {
                            ctx.lineTo(point.x, point.y);
                        }
                    } else {
                        ctx.moveTo(this.x, this.y);
                        ctx.lineTo(this.x, this.y);
                    }
                    ctx.strokeStyle = this.color;
                    ctx.lineWidth = this.size;
                    ctx.lineCap = "round";
                    ctx.stroke();
                    ctx.restore();
                }
            }

            let fireworks = [];
            let explosions = [];


            function launchFirework() {
                const x = rand(maxx * 0.1, maxx * 0.9);
                const y = maxy;
                const targetY = rand(maxy * 0.1, maxy * 0.4);
                const color = randColor();
                const speed = rand(4, 8);
                const size = rand(2, 5);
                fireworks.push(new Firework(x, y, targetY, color, speed, size));


                const timeout = rand(300, 800);
                setTimeout(launchFirework, timeout);
            }

            launchFirework();

            function animate() {
                ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
                ctx.fillRect(0, 0, maxx, maxy);

                for (let i = fireworks.length - 1; i >= 0; i--) {
                    const firework = fireworks[i];
                    if (!firework.update()) {
                        fireworks.splice(i, 1);
                    } else {
                        firework.draw(ctx);
                    }
                }

                for (let i = explosions.length - 1; i >= 0; i--) {
                    const particle = explosions[i];
                    particle.update();
                    if (particle.isAlive()) {
                        particle.draw(ctx);
                    } else {
                        explosions.splice(i, 1);
                    }
                }

                requestAnimationFrame(animate);
            }

            animate();

            window.addEventListener("click", function (event) {
                const x = event.clientX;
                const y = maxy;
                const targetY = event.clientY;
                const color = randColor();
                const speed = rand(4, 8);
                const size = rand(2, 5);
                fireworks.push(new Firework(x, y, targetY, color, speed, size));
            });
        });

    </script>

</body>

</html>

CSS 样式

  • *: 重置所有元素的边距、填充,设置 box-sizing 为 border-box,并统一字体为“Poppins”。
  • body: 设置页面的显示方式、对齐方式、最小高度和背景色。
  • canvas: 设置 canvas 元素的样式,使其覆盖整个屏幕。

JavaScript 部分

1、初始化 Canvas:

创建一个 canvas 元素,并将其添加到页面中。 设置 canvas 的宽度和高度为窗口的宽度和高度。 添加窗口大小调整事件监听器,以确保 canvas 大小随窗口变化。

2、定义辅助函数:

rand(min, max): 生成一个指定范围内的随机浮点数。 randInt(min, max): 生成一个指定范围内的随机整数。 randColor(): 生成一个随机的 HSL 颜色。

3、定义粒子类(Particle):

Particle 类表示一个粒子,具有位置、速度、颜色、重力、摩擦力、透明度等属性。 update()方法更新粒子的位置和透明度。 draw(ctx)方法在 canvas 上绘制粒子。 isAlive()方法检查粒子是否仍然可见。

4、定义烟花类(Firework):

Firework 类表示一个烟花,具有位置、目标高度、颜色、速度等属性。 update()方法更新烟花的位置,并检查是否到达目标高度。 explode()方法在烟花到达目标高度时生成爆炸效果,创建多个粒子。 draw(ctx)方法在 canvas 上绘制烟花的轨迹。

5、烟花和爆炸数组:

fireworks: 存储当前所有烟花的数组。 explosions: 存储当前所有爆炸粒子的数组。

6、发射烟花:

launchFirework()函数随机生成一个烟花,并设置一个定时器以随机时间间隔再次发射烟花。 页面加载时调用 launchFirework()函数。

7、动画循环:

animate()函数是动画的主循环,负责清除屏幕、更新和绘制所有烟花和爆炸粒子。 使用 requestAnimationFrame 实现循环。

8、用户交互:

添加点击事件监听器,用户点击屏幕时生成烟花。