一场盛大的礼花献礼——新年快乐

80 阅读1分钟

我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛 新的一年即将到来,我来为掘金的小伙伴们写一个新年礼花,祝大家:“吉时吉日吉如风,丰年丰月如丰增。增福增禄增长寿,寿山寿水寿长生。生财生利生贵子,子孝孙贤代代荣。荣华富贵年年有,有钱有势有前程!

点击会有惊喜哦~~

接下来写一下实现吧~

首先需要准备一下canvas环境,然后先写一个烟花类,再写一个图片烟花类,然后通过神奇的魔法让他们跑起来就好啦~。本篇文章是基于上一篇普通礼花进阶而来,并对其中代码进行了重构优化。

<template>
  <div>
    <canvas id="canvas"></canvas> //canvas
    <img v-for="i in imgs" style="display: none" :src="i" /> //用H5来方便拿到图片信息
  </div>
</template>

<script setup lang="ts">
import { onMounted, reactive } from "vue";
//本来我是本地图片的,但是码上掘金不能存图片,换成base64又太大,所以我就放到了云上
let xinnian =
  "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-6ea28e5a-1ba5-42e8-adfb-ce99a0b36862/0e4a04f4-72f2-4a9b-9dd0-faadcb5c0e6e.png";
let tunian =
  "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-6ea28e5a-1ba5-42e8-adfb-ce99a0b36862/be45e2c7-c3d9-4376-8a88-1ba04f67b808.webp";
let tunvlang =
  "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-6ea28e5a-1ba5-42e8-adfb-ce99a0b36862/2a88a8fd-eca0-4fa1-9ca2-1634e648fd38.png";
const imgs = reactive([tunian, tunvlang, xinnian]); //其实不用响应式也行
//TS类型
let ctx: CanvasRenderingContext2D; 
let canvas: HTMLCanvasElement;
let img: NodeListOf<HTMLImageElement>;
onMounted(() => {
  img = document.querySelectorAll("img") as NodeListOf<HTMLImageElement>;
  img.forEach((i) => {
  //因为使用了云图片,所以会产生跨域问题
    i.crossOrigin = "";
  });
  init();
  autoFire();
});
let init = () => {
//初始化一下项目
  canvas = document.querySelector("#canvas") as HTMLCanvasElement;
  ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
  resizeCanvas();
};

interface color {
  R: number;
  G: number;
  B: number;
}
interface Firework {
  X: number;
  Y: number;
  radius: number;
  alpha?: number;
  color?: color;
  level?: number;
  size?: number;
}
//和上一篇类似
class Fireworks {
  public X; //烟花的爆炸点X
  public Y; //烟花的爆炸点Y
  public radius; // 烟花爆炸的半径
  public alpha; // 烟花透明度(默认为1)
  public color; //烟花颜色(默认随机)
  public level; //爆炸几层(默认为2)
  public size; //烟花颗粒大小

  constructor({ X, Y, radius, alpha, color, level, size }: Firework) {
    this.X = X;
    this.Y = Y;
    this.radius = radius;
    this.alpha = alpha || 1;
    this.color = color || this.randomColor();
    this.level = level || 2;
    this.size = size || 2;
  }
  public run() {
    this.radius += 2;
    this.Y += this.radius / 50;
    this.alpha -= 0.02;
  }
  private randomColor() {
    let R = Math.floor(Math.random() * 256);
    let G = Math.floor(Math.random() * 256);
    let B = Math.floor(Math.random() * 256);
    return { R, G, B };
  }
  public drawFireworks = (
    fireArr: Fireworks[],
    ctx: CanvasRenderingContext2D
  ) => {
    this.run();
    //烟花
    let count = 20;
    for (let i = 0; i < count; i++) {
      let angle = (360 / count) * i; //烟花粒子角度
      let radians = (angle * Math.PI) / 180; //烟花粒子弧度
      let vx = this.X + Math.cos(radians) * this.radius;
      let vy = this.Y + Math.sin(radians) * this.radius;

      if (this.alpha <= 0 && this.level > 1) {
        fireArr.push(
          new Fireworks({
            X: vx,
            Y: vy,
            radius: 1,
            alpha: 0.8,
            level: this.level - 1,
            size: this.size / 2,
          })
        );
      }
      ctx.beginPath();
      ctx.arc(vx, vy, this.size, 0, Math.PI * 2, false);
      ctx.closePath();
      ctx.fillStyle = `rgba(${this.color.R},${this.color.G},${this.color.B}, ${this.alpha})`;
      ctx.fill();
    }
  };
}
//图片烟花类
class imgFireworks extends Fireworks {
  public img;
  public data;
  public width;
  public height;
  public speed;
  public arr;
  constructor(options: Firework, img: HTMLImageElement) {
    super(options); //继承
    this.img = img;
    ctx.drawImage(this.img, this.X, this.Y);
    let { data, width, height } = ctx.getImageData(
      this.X,
      this.Y,
      this.img.width,
      this.img.height
    );
    ctx.clearRect(this.X, this.Y, width, height);
    this.data = data;
    this.width = width;
    this.height = height;
    this.speed = 1;
    let step = 6;
    this.arr = [];
    for (let h = 0; h < this.height; h += step) {
      for (let w = 0; w < this.width; w += step) {
        let i = (h * this.width + w) * 4;
        let r = this.data[i];
        let g = this.data[i + 1];
        let b = this.data[i + 2];
        let a = this.data[i + 3];
        if (r + g + b == 0) continue;
        this.arr.push({
          color: { r, g, b, a },
          fx: this.X + w - this.width / 2,
          fy: this.Y + h - this.height / 2,
          x: this.X,
          y: this.Y,
        });
      }
    }
  }
  public run() {
    this.speed += 0.2;
    this.alpha -= 0.005;
  }
  public drawFireworks = (
    fireArr: Fireworks[],
    ctx: CanvasRenderingContext2D
  ) => {
    this.run();

    this.arr.forEach((i) => {
      i.x += (i.fx - i.x) / 20;
      i.y += (i.fy - i.y) / 20;
      ctx.fillStyle = `rgba(${i.color.r},${i.color.g},${i.color.b}, ${this.alpha})`;
      ctx.fillRect(i.x, i.y, 1, 1);
    });
  };
}

let autoFire = () => {
  setInterval(() => {
    let X = canvas.width * Math.random();
    let Y = canvas.height * Math.random();
    toRun(
      new Fireworks({
        X,
        Y,
        radius: 10,
        alpha: 1,
        level: 2,
      })
    );
  }, 500);
};
let fireArr: Fireworks[] = []; //烟花的数组

let clickHandler = (e: MouseEvent) => {
  let X = e.clientX;
  let Y = e.clientY;
  if (Math.random() > 0) {
    clearCanvas();
    //展示图片
    toRun(
      new imgFireworks(
        {
          X,
          Y,
          radius: 10,
          alpha: 1,
          level: 2,
        },
        img[Math.floor(img.length * Math.random())]
      )
    );
  } else {
    clearCanvas();
    toRun(new Fireworks({ X, Y, radius: 10, alpha: 1, level: 2 }));
  }
};
let isRun: boolean = false;
function toRun(fire: Fireworks) {
  clearCanvas();
  fireArr.push(fire);
  if (!isRun) {
    RunFire(fireArr);
    isRun = true;
  }
}
document.addEventListener("click", clickHandler);
let resizeCanvas = () => {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  clearCanvas();
};
let clearCanvas = () => {
  ctx.fillStyle = "#000000";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
};
window.addEventListener("resize", resizeCanvas);
//烟花的渲染
let RunFire = (arr: Fireworks[]) => {
  let tick = () => {
    ctx.globalCompositeOperation = "destination-out";
    ctx.fillStyle = `rgba(0,0,0, ${0.2})`;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.globalCompositeOperation = "lighter";
    clearCanvas();
    arr.forEach((i, index) => {
      if (i.alpha <= 0) {
        arr.splice(index, 1);
      }
      i.drawFireworks(arr, ctx);
    });
    requestAnimationFrame(tick);
  };
  tick();
};
</script>

<style lang="scss" scoped>
#canvas {
  border: 1px solid #aaa;
}
</style>

本篇写的是canvas写的烟花,那接下来还要写一篇threeJs的烟花,敬请期待~