这个国庆,带老婆去看一场烟花雨

2,067 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

烟花秀

从2010年起,湖南省长沙市委、市政府提出了推进城市国际化、把长沙打造成具有国际影响力的文化名城的目标,在长沙橘子洲头每周六晚上举办一场烟花秀,当初有幸到现场观看过一次,终身难忘。本想这次国庆节再去观摩一场,无奈疫情反反复复,为了不给国家和人民添乱,就只能在家通过代码的形式再来看一次了,虽然没有现场那么绚丽多彩,但胜在纯手工打造。接下来就陪着老婆一起来看这场烟花秀吧!

实现思路

要实现这么一个绚丽烟花的效果,需要的前置知识很多,包括了三角函数js中的二进制运算等,这些听起来很虚,大家不要慌,接下来我们就一条一条抽丝拨茧,让大家彻底学会这个烟花效果。

因为这个效果是通过 canvas 来实现的,所以我们首先需要学习一下如何通过 canvas 相关的 api 画出一个烟花来。在最初展示的例子中,我们的烟花其实就是一个圆,然后有一个拖尾的效果,并且会自动上升到天空中,最后会有一个爆炸效果。

那么我们先来看一下如何实现一个圆?

首先需要在页面中添加一个 canvas 标签,然后通过 js 获取到这个 canvas 标签,并且获取到它的 2d 属性,我们这里只涉及到 2d 的内容,因此可以通过 getContext('2d') 获取到相关的对象,当然 getContext() 中的参数也支持其它的内容,但是涉及的内容太复杂,这里就不做过多介绍了。

获取到 canvas 画布后,我们就需要借助 canvas2d 属性相关的方法来绘制圆了,下面一起来看一下相关的代码。

我们先在页面中添加一个 canvas 标签,html 如下:

<canvas id="canvas"></canvas>

添加完 canvas 标签后,我们就需要来写 js 代码了,如下:

// 获取到 canvas 元素
const canvas = document.getElementById('canvas');
// 获取 2d 属性
const ctx = canvas.getContext('2d');
// 设置 canvas 的宽高,如果不设置,默认是的宽是300,高是100
canvas.width = innerWidth;
canvas.height = innerHeight;
// 开始绘制
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.arc(100, 100, 30, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();

上述代码中都添加了相关的注释,理解起来比较简单。需要注意的是,开始绘制时要使用 ctx.beginPath(),这主要是为了让前一次绘制和后一次绘制的图形分开,大家可以试试不使用这个方法会产生什么样的效果。

然后我们还使用到了 ctx.arc() 方法,它主要就是用来绘制一个圆的,其中的参数有共有6个,前5个是必填项,最后一个是选填项,使用方法是这样的:

ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

x 表示:圆弧中心(圆心)的 x 轴坐标。

y 表示:圆弧中心(圆心)的 y 轴坐标。

radius 表示:圆弧的半径。

startAngle 表示:圆弧的起始点,x 轴方向开始计算,单位以弧度表示。

endAngle 表示:圆弧的终点, 单位以弧度表示。

anticlockwise 表示(可选):可选的Boolean值 ,如果为 true,逆时针绘制圆弧,反之,顺时针绘制。

知道了 ctx.arc() 的使用方法后,我们最终通过上述代码实现的效果如下图所示:

image.png

前面说了我们绘制烟花是用圆来绘制,这里是为了讲解 ctx.arc() 的使用,因此设置的圆弧半径为30,实际开发中我们只需要将半径设置为3即可,因为烟花本身弹射出来时并不大。

接下来我们就需要让这个烟花动起来了,那么该怎么实现呢?其实也很好解决,之前我们做动画效果,一般都是使用 setInterval 或者 setTimeout 这两个方法,但是现在浏览器还有一个 requestAnimationFrame 方法可以让我们使用,我们可以看一下相关的介绍:

window.requestAnimationFrame()  告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

简单来说,我们使用 requestAnimationFrame 方法时不需要自己去设定一个执行的时间,浏览器会自动去执行相关的动画,并且这个方法的使用也很简单,下面我们一起来看一下如何让这个烟花向天空弹射出来。

烟花发射

简单来说,如果一个元素想要移动,那么就需要改变它在 x轴 或者 y轴 上面的值,通过不断的改变这两个值,对应的元素就会动起来了,下面我们先来看一下相关的代码,如下:

const canvas = document.getElementById('canvas');
// 获取 2d 属性
const ctx = canvas.getContext('2d');
// 设置 canvas 的宽高,如果不设置,默认是的宽是300,高是100
canvas.width = innerWidth;
canvas.height = innerHeight;
// 随机生成烟花出现的 x轴 位置
let x = Math.random() * canvas.width | 0;
let y = canvas.height;
// 设置一个重力参数,用于确保烟花能够到达页面的最高位置
let vel = -(Math.random() * Math.sqrt(canvas.height) / 3 + Math.sqrt(4 * canvas.height) / 2) / 5;
// 随机生成一个颜色
let color = `hsl(${Math.random() * 360 | 0}, 100%, 60%)`;

上面这些都是一些基础的准备数据,其中的 Math.random() * canvas.width | 0 意思是生成一个随机值为 0canvas 宽度的值,后面的竖线是 js 中的二进制运算符,之所以用竖线运算符,是为了帮助我们获取到整数,因为 Math.random() 随机生成的是从0到1直接的小数,我们不需要小数位,因此这里直接使用竖线运算符是最为方便的,当然也可以使用 parseInt() 或者其它的方法也都是可以的。

除了 Math.random(),还使用到了 Math.sqrt(),那 Math.sqrt() 又是个什么东西呢?这其实是三角函数中用来开平方的,后面我们一起来学习一下三角函数相关的知识,这里知道怎么用就可以了。

有了上述的基本内容后,我们就可以开始来添加动画相关的内容了,我们先修改一下之前的代码,如下:

function draw() {
    ctx.globalAlpha = 1;
    // 开始绘制
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.arc(x, y, 3, 0, Math.PI * 2);
    ctx.fill();
    ctx.closePath();
}

在前面的代码中,我们是直接绘制一个圆,这里我们将这个绘制的方法封装成一个 draw 方法,方便我们后续的使用,接下来我们就需要有一个更新的方法了,主要用于不断的更新绘制圆中的 y轴 信息,这样圆球就能动起来了,代码如下:

function update() {
    y += vel;
    // 摩擦系数
    vel += 0.04;
    // 当达到最高点时,需要删除当前烟花,重新再绘制一个新的
    if (vel >= 0) {
        x = Math.random() * canvas.width | 0;
        y = canvas.height;
        vel = -(Math.random() * Math.sqrt(canvas.height) / 3 + Math.sqrt(4 * canvas.height) / 2) / 5;
        color = `hsl(${Math.random() * 360 | 0}, 100%, 60%)`;
    }
}

我们定义了一个 update 方法,在这个方法中,通过改变 y轴 的坐标,并且不断改变最开始定义的圆球重力参数,从而使圆球也就是烟花能够到达屏幕最高点,并且在最高点时需要重新绘制一个新的烟花。我们一起来看一下最后关于动画相关的代码,如下:

function animate() {
    requestAnimationFrame(animate);
    ctx.globalAlpha = 0.1;
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    update();
    draw();
}

animate();

在这个 animate 中,我们通过 requestAnimationFrame 方法调用 animate 本身,并且不断的更新重绘这个烟花,在 animate 中,我们没有使用 clearRect() 而是 fillRect(),这样做的好处就是可以让烟花在页面中有一个拖尾效果,上述代码最终实现的效果如下图所示:

face3.gif

到这里,我们的烟花也完成,还剩下什么呢?对了,就是烟花爆炸后的效果,我们需要创建一个爆炸的粒子效果来代替烟花的爆炸效果,那么该怎么实现呢?

别急,还记得我们在前面讲的关于三角函数的内容吗?要制作粒子爆炸效果,就需要用到大量的三角函数的知识,因此接下来我们就先来学习一下三角函数相关的知识吧!

三角函数

三角函数是数学中常见的一类关于角度的函数,一般我们在初中都学过相关的知识点,而在 js 中,三角函数的运用是怎样的呢?

首先来看一下勾股定理a2+b2=c2a^2 + b^2 = c^2

image.png

根据勾股定理可以得出:

角度α30°45°60°90°120°135°150°180°270°360°
弧度oπ/6π/4π/3π/22π/33π/45π/6π3π/2
sinαo1/2√2/2√3/21√3/2√2/21/20-10
cosα1√3/2√2/21/20-1/2-√2/2-√3/2-101
tanαo√3/31√3--√3-1-√3/30-0

通过上面表格中的换算,我们可以得知每个角度对应的弧度,因为在 js 中是没有角度的,所以只能用弧度来计算,因此我们就需要用到对应的换算公式。

我们再来看两个其它的 API

Math.pow() 求平方/立方

Math.sqrt() 开平方

在上图中,如果我们要求 c边 的长度,按照勾股定理,就是前面列出的计算方式,在 js 中则需要使用前面这两个 API 来实现,代码如下:

c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2))

a的平方加b的平方,然后开平方就能得到c的值。

更多关于三角函数的更多内容,可以点击这里进行查看,以上内容也是来自这篇文章。这里就不做过多的介绍了,毕竟我自己也不是很熟悉<( ̄︶ ̄)>

有了三角函数相关的知识点,接下来就需要制作粒子爆炸效果了。

粒子爆炸

要实现粒子爆炸的效果,其实也很简单。跟前面的烟花效果一样,要实现爆炸效果也是改变某个方法的值就可以了,当烟花弹射到最高点时,就需要动态的产生一个粒子了。

我们先来实现一个粒子的绘制,代码如下:

class Particle {
    constructor(x, y, color, canvas, ctx) {
        this.x = x;
        this.y = y;
        this.color = color;

        // 随机生成粒子炸开的方向
        this.vx = (0.5 - Math.random()) * 100;
        this.vy = (0.5 - Math.random()) * 100;
        // 随机生成粒子有消息,来判断每个粒子何时熄灭
        this.age = Math.random() * 100 | 0;
    }

    draw() {
        ctx.globalAlpha = 1;
        ctx.beginPath();
        ctx.fillStyle = this.color;
        ctx.arc(this.x, this.y, 1, 0, Math.PI * 2);
        ctx.fill();
    }

    update() {
        // 同时改变粒子的垂直方向的值,确保粒子会向下运动
        x += this.vx / 20;
        y += this.vy / 20;
        this.vy++;
        this.age--;
    }
}

Particle 中, 除了通过前面的烟花传过来的烟花的 x轴y轴 以及烟花的颜色外,我们还单独定义了两个变量,这两个变量主要用于随机生成粒子炸开的方向。通过不断的修改 x轴y轴 的值,从而实现粒子在空中炸开的效果。

最终我们通过面向对象的方法将代码组织起来,从而实现最初展示给大家看到的烟花效果。

总结

通过学习 canvas 相关的 API 实现了基本的圆,以及烟花的生成;通过 requestAnimationFrame 方法实现动画效果,从而完成了烟花的发射;通过学习三角函数相关的知识,实现了粒子的爆炸效果,并且还学习了 js 中的二进制运算,从而简化了我们获取随机整数的方法。

这一节是为我们后续学习更多关于 canvas 绚丽效果打下基础,在后续更多的 canvas 案例中,我们还会反复用到三角函数相关的知识点。

最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家

往期回顾

还记得2048怎么玩吗?快来玩会儿(摸鱼)吧!

canvas 实现七彩炫酷圆环,快来看看

参考文献

MDN Math.random()

window.requestAnimationFrame

js运算符单竖杠“|”的用法

三角(反三角)函数在javascript中的应用