为了表白,我手写了一场烟花

去年的五一,计划是带着我宝回老家见父母的,虽然我和我宝在一起好几个月了,但是还没有给我宝一场浪漫的告白,之前就有计划给我宝放一场专属的烟花,在烟花下去告白。五一是一次难得的机会,因为老家是农村,没有限制放烟花,门口也有很大的空地,很适合放烟花。然而不久便有了个不好的消息,五一前我宝可能要去海南出差,因为太远了,不一定回来,原本的计划有了不确定性。然后我便想着,如果我宝五一去了海南,那么我就给我宝准备一场线上烟花秀。有了想法,便立即行动。

一开始的时候,设想的是有几种烟花,比如只有一种颜色的烟花,有多种颜色的烟花,有图片烟花,有文字烟花等等。所以需要有一个基类Firework,这个基类中定义了烟花的位置,速度,透明度,持续时间,角度。然后就是update和render两个函数,update函数用来更新这些属性的值,当持续时间为0的时候,从烟花数组中移除掉自己,render函数用来渲染烟花。

js 复制代码
import type Vector from "../statute/Vector";

export default class Firework {
    position: Vector;
    speed: Vector;
    color: string = 'fffff';
    alpha: number = 255;
    duration: number = 1000;
    durationLeft: number = 1000;
    radius: number = 2;

    constructor(position: Vector, speed: Vector, color: string, duration: number) {
        this.position = position;
        this.speed = speed;
        this.color = color;
        this.duration = duration;
        this.durationLeft = duration;
    }

    getColor(): string {
        return `#${this.color}${this.alpha.toString(16).padStart(2, '0')}`
    }

    update(fires: Array<Firework>): void {
        this.durationLeft -= 1;
        if (this.durationLeft <= 0) {
            fires.splice(fires.indexOf(this), 1);
            return;
        }
        this.position.x += (this.speed.x * Math.sin(this.durationLeft / this.duration * Math.PI / 2));
        this.position.y += (this.speed.y * Math.sin(this.durationLeft / this.duration * Math.PI / 2));
        this.alpha = Math.floor(255 * Math.sin(this.durationLeft / this.duration * Math.PI / 2));
    }

    render(context: CanvasRenderingContext2D | null | undefined): void {
        if (!context) {
            return;
        }
        const x = this.position.x;
        const y = this.position.y;
        context.beginPath();
        context.fillStyle = this.getColor();
        context.arc(x, y, this.radius, 0, Math.PI * 2, false);
        context.fill();
        context.closePath();
    }
}

然后是具体到每一种类型的烟花。

第一种是单色烟花,说是单色,其实也支持多色。首先是发射的烟花,就是从地面升起到爆炸这一阶段的烟花,这个类只是重写了update函数,让它快速往上跑,当要爆炸的时候,就是往烟花数组里插入很多个烟花(爆炸散射出来的烟花),然后销毁自己。这里其实也挺麻烦,爆炸开的烟花要多均匀一些,但是不能太过均匀,否则看起来就太假了,每个角度的烟花需要在一定的范围内随机,尝试了很多次,才得到一个自己比较满意的效果。除此之外呢,爆炸开的烟花速度也不一样,最外面的烟花速度要快一点,越靠近中心,速度越慢,这样爆炸开的烟花就会呈现一个圆形,比较好看。

js 复制代码
static createSingleColorFirework(px: number, py: number, color?: string): Array<Firework> {
    const fires: Array<Firework> = [];
    const r = 5;
    let fireworkColor = color ?? FireworksUtil.colors[FireworksUtil.randomInt(0, FireworksUtil.colors.length)];
    const speeds = [5, 4.2, 3.4, 2.6, 1.8, 1];
    speeds.forEach(speed => {
        const count = speed * 4;
        for (let i = 1; i <= count; i++) {
            const s = speed * this.random(0.9, 1);
            const angle = i * Math.PI * 2 / count + this.random(-1 / (Math.PI * 2), 1 / (Math.PI * 2));
            const sx = s * Math.cos(angle);
            const sy = s * Math.sin(angle);
            const firework = new LoveFirework({ x: px, y: py }, { x: sx, y: sy }, fireworkColor, FireworksUtil.randomInt(30, 50));
            firework.type = 'single';
            fires.push(firework);
        }
    });
    return fires;
}

多色烟花其实和单色烟花差不多,只是把颜色随机一下,也随机一点,均匀一点就行。

第二种烟花是图片烟花,图片烟花关键就是先把图片画到画布上,然后用getImageData把图片的像素信息提取出来生成烟花即可,因为图片的像素比较高,如果都取出来生成烟花会很影响性能,所以这里的图片大小是调整过的,比较小,烟花在屏幕上也没有那么大,所以用大图片没必要。然后就是取像素的时候隔几个像素取就行了。

js 复制代码
static createImageFirework(path: string, px: number, py: number): Promise<Array<Firework>> {
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = path;
        image.onload = function () {
            const fires: Array<Firework> = [];
            const canvas = document.createElement('canvas');
            canvas.width = image.width;
            canvas.height = image.height;
            const ctx = canvas.getContext('2d');
            ctx?.drawImage(image, 0, 0);
            const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height);
            const radius = Math.floor((canvas.width >= canvas.height ? canvas.height : canvas.width) / 2);
            for (let r = 1; r <= radius; r = r + 4) {
                const speed = r * 0.1;
                const count = r * 3;
                for (let a = 0; a < Math.PI * 2; a = a + Math.PI * 2 / count) {
                    const s = speed * FireworksUtil.random(0.9, 1);
                    const x = Math.floor(radius + r * Math.cos(a));
                    const y = Math.floor(radius - r * Math.sin(a));
                    const sx = s * Math.cos(a);
                    const sy = -s * Math.sin(a);
                    const index = (y * image.width + x) * 4;
                    const red = imageData?.data[index] ?? 0;
                    const green = imageData?.data[index + 1] ?? 0;
                    const blue = imageData?.data[index + 2] ?? 0;
                    const color = FireworksUtil.convertColor(red, green, blue, 255);
                    const firework = new ImageFirework({ x: px, y: py }, { x: sx, y: sy }, color, FireworksUtil.randomInt(30, 50));
                    firework.white = false;
                    fires.push(firework);
                }
            }
            resolve(fires);
        }
    });
}

第三种是文字烟花,文字烟花和图片烟花其实是差不多的,只是一开始需要将文字绘制到画布上,然后从画布上获取像素信息,和图片烟花一样的处理逻辑。

效果如下

因为时间的关系,最后就做出来这几种烟花。然后放到了我宝和我的网站上,设置好触发条件,等到5月20号的时候,我宝登录我们的网站,就会进入烟花的页面,给她来一场烟花秀。

然而事情出现了变化。出差延迟了,我宝可以和我一起回老家了,线上烟花用不上了。然而事情又出现了变化。我宝觉得烟花虽美,然而转瞬即逝。拗不过我宝,只能打消了,所以我的线上烟花又能派上用场了

相关推荐
浮华似水12 分钟前
简洁之道 - React Hook Form
前端
正小安2 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光4 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   4 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   4 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web4 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常4 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇5 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr5 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui