我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛
新的一年即将到来,我来为掘金的小伙伴们写一个新年礼花,祝大家:“吉时吉日吉如风,丰年丰月如丰增。增福增禄增长寿,寿山寿水寿长生。生财生利生贵子,子孝孙贤代代荣。荣华富贵年年有,有钱有势有前程!
”
点击会有惊喜哦~~
接下来写一下实现吧~
首先需要准备一下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
的烟花,敬请期待~