canvas系列三之《烟花》效果实现

6,878 阅读7分钟

canvas系列三之烟花效果实现

写在最前:

  唉,时间过得真是快啊,距离上次写demo已经是半个月前了,原来想得是每周写一篇的,但是前段时间工作微忙,所以就没有demo产出(嘿嘿,偷偷的给自己找了个借口,还不知道以后能不能坚持产出呢......)。这次写的还是canvas的东西,利用canvas写了一个燃放烟花的demo。感觉差不多再写一两个demo我就会对这个东西做一下最后的总结吧,毕竟老写demo也不是个事儿,总得总结下canvas这个东东。好了,废话不多说,我们直入主题

  对了,大佬们记得给我的博客点赞,要星星哦~

成果展示

Git地址:

github.com/ry928330/fi…

实现思路:

  照例,我们还是用一种抛出问题的方式给出该次烟花demo的实现过程,小伙伴们在阅读的时候也可以通过这些问题自己想象可以怎么实现。

  • 烟花的产生与消失,主要就是怎么来实现那种流星划过般的拖尾效果?
  • 烟花消失后,如何产生爆炸后四散的效果?
  • 四散的花火如何产生那种受"重力"影响而降落的效果以及花火的消失?

  基本上,你做到了上述的三步,你的烟花效果就算是做出来了,哈哈,很简单吧。下面,我们逐个来解决。

详细说明:

烟花的产生与消失:

  看过我之前canvas系列之一的伙伴们,或者对canvas动画有了解的同学肯定知道在canvas里面绘制的一个元素它是如何动起来的吧。我们是不是不断利用clearReact函数去帮助我们清除前一帧的动画。其实,我们如果不完全清除前一帧的动画,而是用一个比较透明的颜色去“盖住”呢?哈哈哈,是不是很简单,其实就一句代码:

ctx.fillStyle = 'rgba(0,0,0,0.05)';

  你想拖的尾比较长,你就把透明度改得小一些,如果你想拖得尾短一些,你就把透明度改得大一点。接下来说一说,烟花的消失。对于每个烟花,我们将其声明为一个对象,里面包括烟花的大小,烟花的位置,以及烟花的绘制于移动函数。其中,还有一个变量disappear是用来表示烟花的存在于消失的,初始值是false,表示烟花还“活着”。我们可以设置一些边界条件,本次demo采用的是距离边界,即当烟花移动到我设定的范围以外,就将变量disappear设置为true,表示烟花已跪。因为是要不断的产生烟花,所以我用了一个数组来存储已有的烟花数。新的烟花不断被push进数组,消失的烟花就不断从数组中移除。贴下我的代码:

if (fireArr.length) {
    fireArr.forEach(function(item, index) {
        var marginWidthLeft =  parseInt(getRandom(0, canvas.width/5), 10);
        var marginWidthRight = parseInt(getRandom(1500, canvas.width), 10);
        var marginHeight = parseInt(getRandom(0, 300), 10);
        if (item.x >= marginWidthRight || item.x <=  marginWidthLeft || item.y <= marginHeight) {
            item.disappear = true;
        }
        if (!item.disappear) {
            item.draw();
            item.move();
        } else {
            var removeFire = fireArr.splice(index, 1);
            fragments.push(removeFire);
            if (fragments.length) {
                fragments.forEach(function(item, index) {
                    if (item[0].boomJudge) {
                        item[0].boom();
                        item[0].boomJudge = false;
                    }
                })
            }
            fireArr.push(createRandomFire(CreateFireObj));
        }
    })
}

  这里我设置了左右和上边界,都取得是一定范围内的随机值。当disappear值变为true的时候就从数组中移除相应的元素,之后再添加新的元素,保证页面内是有固定数量的烟花。

烟花消失,爆炸效果的产生:

  这是本次demo的难点,也是重点。就是怎么刚好在烟花消失的时候同时产生许许多多的小烟花呢?小烟花又是如何炸裂开来的呢?刚开始我也是一头雾水的,后来想着想着就慢慢的有些思路了。

  对于每个烟花,我给其增加了一个爆炸函数boom,这个boom函数的作用就是在烟花消失也即爆炸的时候,瞬间产生随机数量的小花火(其实这个数量我们可以随机控制在一个范围之内)。对于每个小花火,其颜色随机,而且在它生的时候我为其生成了一个它所能到达的目标位置。这个目标位置是以烟花爆炸的位置为圆心,半径随机,利用圆的方程,给一个随机的角度,这样小火花产生后就可以在以烟花爆炸位置为圆心,随机半径的圆弧线的随机位置出现。为了便于管理,每个烟花都有一个数组变量fragArr,用于存储这些新产生的小花火。每个小花火也都是一个对象,同样,与烟花类似,小花火对象里面存储了花火的大小、位置、移动函数、绘制函数以及运动终止位置等信息。相关代码如下:

//烟花爆炸,产生碎片
this.boom = function() {
    var scope = Math.round(getRandom(10, 40));
    for (var i=0; i<scope; i++) {
        var angel = getRandom(0, 2*Math.PI);
        var range = Math.round(getRandom(50, 300));
        var targetX = this.x + range*Math.cos(angel);
        var targetY = this.y + range*Math.sin(angel);
        var r = Math.round(getRandom(120, 255));
        var g = Math.round(getRandom(120, 255));
        var b = Math.round(getRandom(120, 255));
        var color = 'rgb(' + r + ',' + g + ',' + b + ')';
        var frag = new CreateFrag(this.x, this.y, color, targetX, targetY);
        this.fragArr.push(frag);
    }
}

四散烟花的降落及其消失:

  接着上面,在爆炸瞬间我们利用boom函数产生了数量随机的小花火,每个小花火利用函数CreateFrag生成。我们重点来看下这个函数里面的关于小火花的移动函数,因为是它产生了小火花如“重力”般下降的效果,代码如下:

function CreateFrag(x, y, color, tx, ty) {
    var that = this
    that.x = x;
    that.y = y;
    that.ty = ty;
    that.tx = tx;
    that.color = color;
    that.disappear = false;
    that.draw = function() {
        ctx.save();
        ctx.beginPath();
        ctx.fillStyle = that.color;
        ctx.fillRect(that.x, that.y, 2, 2);
        ctx.restore();
    }
    that.move = function() {
        that.ty = that.ty + 0.5;
        var dx = that.tx - that.x, dy = that.ty - that.y;
        that.x = Math.abs(dx) < 0.1 ? that.tx : (that.x + dx*0.01);
        that.y = Math.abs(dy) < 0.1 ? that.ty : (that.y + dy*0.01);
        if (dx == 0 || dy == 0 || that.y >= 700 || that.x <= 300 || that.x >= 1700) {
            that.fragDisappear = true;
        }
    }
}

  函数接受了五个参数,后面两个tx、ty,代表了其目标点的x、y坐标,我们给出了一个他的当前位置和目标位置的差值,dx和dy,在这个差值的范围未超出某个给定的阈值(我们这里给得是0.1)时,小火花的横纵坐标都增加dx和dy的0.01倍,这个倍数越小,越能表现出烟花“爆炸”的细节。这个我们给到一个比较合适的值。小花火的“重力下降”其实得益于一句简单的代码,即:that.ty = that.ty + 0.5。如果没有这句代码,小花火的移动路线是直线,加了这句之后,相当于其垂直速度的增加速度是不断增大的,这就相当于给了它一个垂直方向的加速度,让其看起来像受“重力”牵引而不断下降一样。对于,花火的消失,其实和烟花的消失原理差不多,都是花火达到某个边界时让其消失掉,但是不同的是当烟花的一组花火中的某一个达到边界条件的时候,我就将该朵花火所在的整个数组干掉,而不是一个一个花火单独去除,贴下代码:

var removeFire = fireArr.splice(index, 1);
fragments.push(removeFire);
if (fragments.length) {
    fragments.forEach(function(item, index) {
        if (item[0].boomJudge) {
            item[0].boom();
            item[0].boomJudge = false;
        }
    })
}
if (fragments.length) {
    fragments.forEach(function(item1, index1) {
        item1[0].fragArr.forEach(function(item2, index2) {
            if (item2.fragDisappear) {
                fragments.splice(index1, 1);
            }
            item2.draw();
            item2.move();
        })
    })
}

  fragments数组里面存储的是被移出的烟花,这里我们要注意一点,在fragments.forEach里面用的是item1[0],而不是item1,是因为在执行废弃烟花的存储操作的时候,即var removeFire = fireArr.splice(index, 1);
fragments.push(removeFire); fragments push的是fireArr splice的返回值,是一个含有单个元素的数组。

  经过这样简单的三部曲之后,我们的烟花就可以绚丽多姿的在空中爆炸了,怎么样,现在回头看看是不是觉得很简单呢。

写在最后:

  上述只是实现了一个简单的烟花产生到爆炸的效果,其实还有很多地方是可以改进的。你比如说,在烟花上升的过程中,我们可以不通过限制它只能在某个范围内运动,而是通过“重力”的影响,使其垂直速度减为0的时候,产生爆炸效果。还有我们可以添加一段音频,模拟烟花爆炸的声音,等等。这些都是值得改进的地方。最后,谢谢大佬们百忙之中阅读我的这片分享,请一定要给我的博客点赞哦,你的点赞会成为我前进的不竭动力~