Advertisement

「Canvas 动画 」- 烟花

阅读量:

前言

在开发之前,我们要了解一下烟花从上升到爆炸的过程:

复制代码
 设定烟花初始位置,爆炸位置,朝终点飞行
    2. 受重力作用, 上升速度逐渐减慢
    3. 烟花到达爆炸位置,爆炸散发出火花,烟花圆点消失
    4. 火花以爆炸位置为圆心,向四周扩散,并且高度缓慢下降
    5. 经过短暂时间,火花消失

由此,我们可以先列出主要对象和属性:

  • 烟花绽放
  • 当前时间点坐标: (x, y)
  • 颜色: color
  • 大小半径: radius
  • 带有火苗: booms
  • 爆炸发生点: boomPoint
  • 未爆炸状态: dead

火花 )
实时显示位置坐标(x,y)
爆炸点的中心位置(centerX, centerY)
飞行目标的位置坐标(dieX,dieY)
颜色参数color
半径值radius
是否已经消失?dead

基本框架

复制代码
    const canvas = document.querySelector("#canvas")
    const ctx = canvas.getContext("2d")
    const _W = canvas.width = window.innerWidth
    const _H = canvas.height = window.innerHeight
    
    class FireWork {
      constructor(x, radius, color, boomPoint) {
    this.x = x
    this.y = _W
    this.radius = radius
    this.color = color
    this.booms = []
    this.boomPoint = boomPoint
    this.dead = false
      }
    }
    
    class Spark {
      constructor(centerX, centerY, radius, color, dieX, dieY) {
    this.dieX = dieX;
    this.dieY = dieY;
    this.x = centerX;
    this.y = centerY;
    this.dead = false;
    this.centerX = centerX;
    this.centerY = centerY;
    this.radius = radius;
    this.color = color;
      }
    }
    // 动画函数,实时清空画布并重新绘制
    const animation = function () {
      window.requestAnimationFrame(animation);
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      // 绘制...
    };
    
    animation()

烟花飞行

复制代码
    const random = function (min, max) {
      return Math.random() * (max - min) + min
    }
    
    class FireWork {
      constructor(x, radius, color, boomPoint) {
    this.x = x
    this.y = _W
    this.radius = radius
    this.color = color
    this.booms = []
    this.boomPoint = boomPoint
    this.boomArea = random(60, 150) // 距离范围
    this.dead = false
      }
    
      move() {
    // 与爆炸位置的距离,逐渐变小
    let dx = this.boomPoint.x - this.x
    let dy = this.boomPoint.y - this.y
    // 跟随距离改变上升速度,逐渐变慢
    this.x = this.x + dx * 0.01
    this.y = this.y + dy * 0.01
    // 后期烟花移动速度过慢,故给一个距离范围,进入范围便爆炸
    if (Math.abs(dx) <= this.boomArea && Math.abs(dy) <= this.boomArea) {
      this.dead = true
    }
      }
    
      draw() {
    ctx.save()
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
    ctx.fillStyle = this.color
    ctx.fill()
    ctx.restore()
      }
    	// 模拟烟花尾随特效
      drawTail() {
    ctx.save()
    ctx.fillStyle = "rgba(186,186,86,0.3)"
    ctx.beginPath()
    ctx.arc(this.x, this.y, random(this.radius, this.radius + 4), 0, 2 * Math.PI)
    ctx.fill()
    ctx.restore()
      }
    
      update() {
    if (!this.dead) {
      this.move()
      this.draw()
      this.drawTail()
    }
      }
    }
    
     const o = new FireWork(_W / 2, 3, "#FFF", {
        x: _W / 2,
        y: 10
    })
    
     const animation = function () {
       window.requestAnimationFrame(animation);
       // 使用 透明度0.1填充画布,实现烟花拖拽特效
       ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
       ctx.fillRect(0, 0, canvas.width, canvas.height);
       o.update()
     };
    
    animation()

火花飞行

复制代码
    class Spark {
      constructor(centerX, centerY, radius, color, dieX, dieY) {
    this.dieX = dieX;
    this.dieY = dieY;
    this.x = centerX;
    this.y = centerY;
    this.dead = false;
    this.centerX = centerX;
    this.centerY = centerY;
    // 火花大小差异化
    this.radius = random(radius, radius + 2);
    this.color = color;
      }
      draw() {
    ctx.save();
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
    ctx.fillStyle = this.color;
    ctx.fill()
    ctx.restore();
      }
      move() {
    // 模拟重力下降
    this.dieY = this.dieY + this.radius / 10
    let dx = this.dieX - this.x
    let dy = this.dieY - this.y
    // 如果距离小于 0.1 则坐标直接等于消失坐标
    this.x = Math.abs(dx) < 0.1 ? this.dieX : (this.x + dx * 0.1);
    this.y = Math.abs(dy) < 0.1 ? this.dieY : (this.y + dy * 0.1);
    // x 达到消失坐标,则消失
    if (dx == 0 && Math.abs(dy) <= 80) {
      this.dead = true;
    }
      }
      update() {
    if (!this.dead) {
      this.move()
      this.draw()
    }
      }
    }

烟花爆炸

复制代码
    class FireWork {
    	// ...省略
      move() {
    let dx = this.boomPoint.x - this.x
    let dy = this.boomPoint.y - this.y
    this.x = this.x + dx * 0.01
    this.y = this.y + dy * 0.01
    if (Math.abs(dx) <= this.boomArea && Math.abs(dy) <= this.boomArea) {
      this.dead = true
      // 烟花到达爆炸点,初始化火花对象
      this.initBoom() 
    }
      }
    
      initBoom() {
    // 随机爆炸数量
    let num = random(50, 200)
    for (let i = 0; i < num; i++) {
      // 随机爆炸角度
      let tan = random(-Math.PI, Math.PI);
      let x = random(0, 400) * Math.cos(tan) + this.x;
      let y = random(0, 400) * Math.sin(tan) + this.y;
      let boom = new Spark(this.x, this.y, random(0, 2), randomColor(), x, y);
      this.booms.push(boom);
    }
      }
    	// 火花爆炸函数
      boomAction() {
    for (let i = 0; i < this.booms.length; i++) {
      if (!this.booms[i].dead) {
        this.booms[i].update();
      }
    }
      }
    
      update() {
    if (!this.dead) {
      this.move()
      this.draw()
      this.drawTail()
    } else {
      this.boomAction()
    }
      }
    }

最终代码

复制代码
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>烟花</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
    </style>
    </head>
    
    <body>
    <canvas id='canvas'></canvas>
    </body>
    <script>
    const random = function (min, max) {
        return Math.random() * (max - min) + min
    }
    const randomColor = function () {
        let colors = [
            "#f44336",
            "#e91e63",
            "#9c27b0",
            "#673ab7",
            "#3f51b5",
            "#2196f3",
            "#03a9f4",
            "#00bcd4",
            "#009688",
            "#4CAF50",
            "#8BC34A",
            "#CDDC39",
            "#FFEB3B",
            "#FFC107",
            "#FF9800",
            "#FF5722",
        ];
    
        let num = Math.floor(Math.random() * colors.length)
        return colors[num]
    };
    const canvas = document.querySelector("#canvas")
    const ctx = canvas.getContext("2d")
    const _W = canvas.width = window.innerWidth
    const _H = canvas.height = window.innerHeight
    
    class FireWork {
        constructor(x, radius, color, boomPoint) {
            this.x = x
            this.y = _W
            this.radius = radius
            this.color = color
            this.booms = []
            this.boomPoint = boomPoint
            this.boomArea = random(60, 150)
            this.dead = false
            this.sparkDead = false
        }
    
        move() {
            // 与爆炸位置的距离,逐渐变小
            let dx = this.boomPoint.x - this.x
            let dy = this.boomPoint.y - this.y
            // 跟随距离改变上升速度,逐渐变慢
            this.x = this.x + dx * 0.01
            this.y = this.y + dy * 0.01
            // 后期烟花移动速度过慢,故给一个距离范围,进入范围便爆炸
            if (Math.abs(dx) <= this.boomArea && Math.abs(dy) <= this.boomArea) {
                this.dead = true
                this.initBoom()
            }
        }
    
        draw() {
            ctx.save()
            ctx.beginPath()
            ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
            ctx.fillStyle = this.color
            ctx.fill()
            ctx.restore()
        }
    
        drawTail() {
            ctx.save()
            ctx.fillStyle = "rgba(186,186,86,0.3)"
            ctx.beginPath()
            ctx.arc(this.x, this.y, random(this.radius, this.radius + 4), 0, 2 * Math.PI)
            ctx.fill()
            ctx.restore()
        }
    
        initBoom() {
            let num = random(50, 200)
            for (let i = 0; i < num; i++) {
                let tan = random(-Math.PI, Math.PI);
                let x = random(0, 250) * Math.cos(tan) + this.x;
                let y = random(0, 250) * Math.sin(tan) + this.y;
                let boom = new Spark(this.x, this.y, random(0, 1), randomColor(), x, y);
                this.booms.push(boom);
            }
        }
    
        boomAction() {
            let deadNum = 1
            for (let i = 0; i < this.booms.length; i++) {
                if (!this.booms[i].dead) {
                    this.booms[i].update();
                } else {
                    deadNum++
                }
            }
            if (deadNum == this.booms.length) this.sparkDead = true
        }
    
        update() {
            if (!this.dead) {
                this.move()
                this.draw()
                this.drawTail()
            } else {
                this.boomAction()
            }
        }
    }
    
    class Spark {
        constructor(centerX, centerY, radius, color, dieX, dieY) {
            this.dieX = dieX;
            this.dieY = dieY;
            this.x = centerX;
            this.y = centerY;
            this.dead = false;
            this.centerX = centerX;
            this.centerY = centerY;
            // 火花大小差异化
            this.radius = random(radius, radius + 1);
            this.color = color;
        }
        draw() {
            ctx.save();
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
            ctx.fillStyle = this.color;
            ctx.fill()
            ctx.restore();
        }
        move() {
            // 模拟重力下降
            this.dieY = this.dieY + this.radius / 10
            let dx = this.dieX - this.x
            let dy = this.dieY - this.y
            // 如果距离小于 0.1 则坐标直接等于消失坐标
            this.x = Math.abs(dx) < 0.1 ? this.dieX : (this.x + dx * 0.1);
            this.y = Math.abs(dy) < 0.1 ? this.dieY : (this.y + dy * 0.1);
            // x 达到消失坐标,则消失
            if (dx == 0 && Math.abs(dy) <= 80) {
                this.dead = true;
            }
        }
        update() {
            if (!this.dead) {
                this.move()
                this.draw()
            }
        }
    }
    
    const particles = []
    let oldTime = new Date()
    
    const animation = function () {
        window.requestAnimationFrame(animation);
        // 使用 透明度0.1填充画布,实现烟花拖拽特效
        ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        // 间隔时间生成烟花
        let newDate = new Date()
        if (newDate - oldTime > random(300, 1500)) {
            let o = new FireWork(random(_W / 4, 0.8 * _W), 3, "#FFF", {
                x: random(_W / 4, 0.8 * _W),
                y: random(50, 200)
            })
            particles.push(o)
            oldTime = newDate
        }
        // 遍历烟花和删除死亡烟花
        particles.forEach((element, index) => {
            element.update()
            if (element.sparkDead) {
                particles.splice(index, 1);
            }
        });
    };
    
    animation()
    </script>
    
    </html>

全部评论 (0)

还没有任何评论哟~