码云笔记前端博客
Home > HTML/CSS > canvas实现绚丽的倒计时效果与动画基础(二)

canvas实现绚丽的倒计时效果与动画基础(二)

2018-12-21 分类:HTML/CSS 作者:码云 阅读(5481)

本文共计10175个字,阅读时间预计26分钟,干货满满,记得点赞加收藏哦

我们要实现的这个demo具有动画的效果,所以接下来将会给大家介绍canvas制作动画的基础内容,推荐大家先阅读《canvas实现绚丽的倒计时效果与动画基础(一)

实现动画的基础函数

实现动画的最简单的架构就是使用setInterval这个方法,关于setInterval方法推荐大家去阅读《定时器setTimeout和setInterval的工作原理以及优缺点》。

1
2
3
4
5
6
7
setInterval(
    function () {
        render();
        update();
    },
    50
)

setInterval方法里面有两个参数,第一个参数传入的是匿名函数,表示在每一帧的时候要做的事情;第二个参数传入一个时间,单位是毫秒,表示每隔多长时间执行一次这个匿名函数。那么大家就可以想象使用setInterval就可以构建出一个逐帧的动画,其中的帧率有这个时间控制,我这里写的50毫秒,那么一秒钟有1000个毫秒,所以这样一个动画帧率就是20。但是事实上这个计算并不是准确的,这是因为这个匿名函数里面执行的内容效率不同不见得能够在20毫秒里完成所有动画的绘制内容,那么这个也是绘画领域内更高级的话题,在以后的课程中有机会的话会向大家介绍更加精确的动画绘制方法,本教程我们使用setInterval方法就好了。

那么,对于这个每一帧执行的函数通常而言要干这么两件事情,第一件事情是绘制当前画面;第二件是根据绘制画面所需要的数据结构对数据结构进行一定的调整。那么我们在这个倒计时的例子中如何使用这个setInterval方法呢

在之前的代码中我们用的是render(context)方法,我们使用setInterval改写,在这里我使用了一个新的函数update()对当前数据进行一个调整,下面重点就是放在如何实现这个update函数。

其实大家只要再仔细分析一下我们这个demo就会发现我们这个动画比较复杂,主要包括两部分的变化,第一部分是时间在逐渐变化,第二部分是时间的变化产生了许多彩色的小球,这些小球产生了物理运动,这样的一个动画效果。

接下来这一小节我们先处理一下最简单时间的变化,大家分析一下可能会发现时间的变化很简单,假如我们不用update处理,再接在绘制render的时候取得当前的时间,然后进行绘制就好了,大家这样想也是完全正确的。但是为了这个结构我们还是把时间的处理放到update中,同时也是为我们下一步随着时间的变化产生小球动画做一个准备,在这里我的主要思路是这样的,首先,我声明一个新的nextShowTimeSeconds变量,这里要注意在render里头是绘制curShowTimeSeconds这个变量所表示的时间,那么在update里面看一下下次显示的时间是多少,具体的还是使用我们之前定义的getCurrentShowTimeSeconds()来获取秒数,之后要看一下下次显示的时间和当前显示的时间有没有变化,一旦有变化的话我们就改变curShowTimeSeconds,具体还要对nextShowTimeSeconds秒数进行时、分、秒的分解。这个产生这种分解不仅有助于产生时间变化的动画,同时后续每一个时间点的变化以后相应的时间点产生这种小球的变化也需要这步分解操作。

接下来我们分别获取nextHours、nextMinutes、nextSeconds,同时根据curShowTimeSeconds获取到curHours、curMinutes、curSeconds,这样我们就可以比较两个时间是不是一样了,具体比较nextShowTimeSeconds和curShowTimeSeconds是否一样,其实只需要比较秒数就可以了,所以我们看一下
nextSeconds是否已经不等于curSeconds,如果已经不等于了,那么我们当前的
curShowTimeSeconds就需要变化,变成我们刚刚算出来的nextShowTimeSeconds:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function update(){

    var nextShowTimeSeconds = getCurrentShowTimeSeconds();

    var nextHours = parseInt( nextShowTimeSeconds / 3600);
    var nextMinutes = parseInt( (nextShowTimeSeconds - nextHours * 3600)/60 )
    var nextSeconds = nextShowTimeSeconds % 60

    var curHours = parseInt( curShowTimeSeconds / 3600);
    var curMinutes = parseInt( (curShowTimeSeconds - curHours * 3600)/60 )
    var curSeconds = curShowTimeSeconds % 60

    if( nextSeconds != curSeconds ){

        curShowTimeSeconds = nextShowTimeSeconds;
    }
}

这里大家可以想象,一旦这个变化产生了,我在这个setInterval里再次执行render函数的时候,在render函数里会会自动对
curShowTimeSeconds进行一个分解,从而显示新的时间。这里大家还需要注意一点,由于我们产生了动画,那么在canvas中进行逐帧动画,每帧都需要把改变的对象进行一次刷新,否则的话,之前的一副图像和新的图像就会叠加在一起,为此我们介绍一个新的函数,这个函数叫clearRect,clearRect函数对一个矩形空间内图形进行一个刷新操作,在这里我们对整个屏幕矩形进行刷新:

1
cxt.clearRect(0,0,WINDOW_WIDTH, WINDOW_HEIGHT);

render函数完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function render( cxt ){

    cxt.clearRect(0,0,WINDOW_WIDTH, WINDOW_HEIGHT);

    var hours = parseInt( curShowTimeSeconds / 3600);
    var minutes = parseInt( (curShowTimeSeconds - hours * 3600)/60 )
    var seconds = curShowTimeSeconds % 60

    renderDigit( MARGIN_LEFT , MARGIN_TOP , parseInt(hours/10) , cxt )
    renderDigit( MARGIN_LEFT + 15*(RADIUS+1) , MARGIN_TOP , parseInt(hours%10) , cxt )
    renderDigit( MARGIN_LEFT + 30*(RADIUS + 1) , MARGIN_TOP , 10 , cxt )
    renderDigit( MARGIN_LEFT + 39*(RADIUS+1) , MARGIN_TOP , parseInt(minutes/10) , cxt);
    renderDigit( MARGIN_LEFT + 54*(RADIUS+1) , MARGIN_TOP , parseInt(minutes%10) , cxt);
    renderDigit( MARGIN_LEFT + 69*(RADIUS+1) , MARGIN_TOP , 10 , cxt);
    renderDigit( MARGIN_LEFT + 78*(RADIUS+1) , MARGIN_TOP , parseInt(seconds/10) , cxt);
    renderDigit( MARGIN_LEFT + 93*(RADIUS+1) , MARGIN_TOP , parseInt(seconds%10) , cxt);
}

这样我们对时间变化的动画就做好了,此时看一下效果:

canvas实现绚丽的倒计时效果与动画基础

由上图我们看到此时这个时间动起来了,一秒一秒逼近我们之前设置的endTime这个倒计时的终点,是不是非常酷。

使用canvas做个物理实验

在为我们demo加上滚动的小球动画前,我们先做一个简单的实验,来看一下我们怎么通过canvas实现小球运动学运动效果,这里我们暂时先处理一个小球动画,为此我声明一个小球类对象:

1
var ball = {x:512, y:100, r:20, g:2, vx:-4, vy:0, color:"#005588"}

来看一下这个小球包含哪些数据,首先是坐标位置我设置为x轴512,y轴100,其次是小球半径为20,之后的g、vx、vy适合运动学相关的参数,我定义加速度g为2,其次速度vx为-4,还有一个速度vy为0,也就是x和Y两个方向的速度,这里的速度我们可以用向量的方式表示,也就是可以用复数表示,之后呢就是小球的颜色。

我们看一下render函数:

1
2
3
4
5
6
7
8
9
10
function render(cxt) {
    cxt.clearRect(0,0,cxt.canvas.width,cxt.canvas.height);

    cxt.fillStyle = ball.color;
    cxt.beginPath();
    cst.arc(ball.x, ball.y, ball.r, 2*Math.PI);
    cxt.closePath();

    cxt.fill();
}

上面的render函数没有什么稀奇的,我们用clearRect将整个屏幕进行clear,这里我是用了context的新的一个属性,通过上下文的绘图环境cxt可以以调用canvas方法来找到这个上下文绘图环境属于哪一块画布(cxt.canvas),之后调用width和height来获得画布的宽高(cxt.canvas.width,cxt.canvas.height),剩下的代码就是对一个小球的绘制。

那么,真正实现运动学效果的关键在于update部分:

1
2
3
4
5
function update() {
    ball.x += ball.vx;//小球的x坐标加上它的x轴速度值
    ball.y += ball.vy;//小球的y坐标加上它的y轴速度值
    ball.vy += ball.g;//小球在y轴的速度值加上重力加速度g
}

这个update的部分就是对小球位置的改变,这段代码非常简单,ball.x表示小球的x坐标加上它的x轴速度值ball.vx,ball.y表示小球的y坐标加上它的y轴速度值ball.y,之后由于重力加速度的影响,这个重力加速度只影响在y轴方向的速度,所以我们对小球在y轴的速度值又加上了它的重力加速度g,此时我们看一下效果:

使用canvas做个物理实验

通过这个简单的例子相信大家了解了,所谓的在canvas实现小球运动学效果,只不过是之前我们已经学过的物理公式,把小球的位置一帧一帧的计算出来。对于这个代码相信大家可以想到很多可以实验的地方,改变(g:2, vx:-4, vy:0,)这三个参数就可以产生不同的效果,比如想让小球落得慢点我们就可以把重力加速度g值改的小点,再比如说刚才我们让小球以左边抛物线运动,那么我们想让它以右边抛物线运动呢,vx就应该为正的。如果想让左边运动更大一些呢,vx值就相应的增大。再有刚才我们的小球在Y方向没有一个上抛的过程,我在这里给大家简单的实验一下,如果我把vy值改成“-10”的话,它就会有一个上抛的过程,如下图:

canvas实现小球运动学效果

对于这个demo大家可以根据自己的喜好在实验一下不同参数会产生的不同效果。

现在我们来考虑另外一个问题,刚才我们的小球落下的时候就消失在画布的外面了,假如我们画布下面是一个地板的话,小球落下来会进行一个反弹,怎么做呢?这就是一个最简单的碰撞检测的过程。在这里我们可以想象,我们可以对小球的位置做一个判断,如果此时我们发现对小球y坐标的位置已经比当前的屏幕的高度就是“768”减去小球的半径还要大了,那么说明此时小球的底部已经触碰到屏幕的边缘了,此时小球的位置就应该就应该发生改变,那么在第一秒的时候小球的位置应该就等于这个768减去小球的半径(768-ball.r),也就是说它先着地,然后最关键的是小球的速度也应该发生转变,此时小球的速度ball.vy等于它原来速度相反的值。

1
2
3
4
5
6
7
8
9
10
function update() {
    ball.x += ball.vx;
    ball.y += ball.vy;
    ball.vy += ball.g;

    if(ball.y >= 768-ball.r){
        ball.y = 768-ball.r;
        ball.vy = -ball.vy;
    }
}

我们看一下效果:

小球弹起来

可以看到小球落到地下就反弹了起来,刚才的代码只是对屏幕的下边缘进行了判断,那么大家学会了这个思路就可以试着对屏幕的上边缘、左边缘、右边缘都进行一个碰撞判断,有兴趣的同学可以下去尝试一下。

接下来我们要解决另外一个问题了,从上图可以看到小球每次反弹可以达到小球所能达到的最高位置,这不符合我们的要求,查一下代码可以发现问题出在“ball.vy = -ball.vy;”,(ball.vy)反弹小球的速度是没有损耗的,这个呢是不符合我们真实地物理现象的,这里我给它加一个系数即-ball.vy*0.5,意思就是一次反弹小球的速度损失0.5速度,此时小球的反弹效果就会更逼真了,一起看一下效果:

小球的反弹效果

是不是非常的酷,那么大家掌握了这些知识就可以继续做我们的绚丽的倒计时效果了。

华丽的小球滚动效果

首先我们要存储这些生成的小球,我声明一个balls的数组,初始化是一个空的数组

1
var balls = [];

之后我们一旦产生新的小球直接添加到这个数组里就可以了,另外我们这些小球呢都是彩色的,为此我有设置了一个colors数组:

1
const colors = ["#33B5E5","#0099CC","#AA66CC","#9933CC","#99CC00","#669900","#FFBB33","#FF8800","#FF4444","#CC0000"]

在这里我存储了10个颜色,那么在具体生成小球的时候我会在这个数组里随机抽取颜色来为小球附上相应的颜色值。下面我们来写一下小球生成的代码,大家可以想象一下,我们把这个生成的代码应该放到update里,因为update是负责数据的改变,而render只是负责绘制,那么生成小球是数据上的改变,我们调在update相应的位置,同样相应位置也应该放在if里面:

1
2
3
if( nextSeconds != curSeconds ){
    curShowTimeSeconds = nextShowTimeSeconds;
}

一旦我们的时间发生了改变,就要根据当前时间的改变来生成一系列的小球,具体生成过程还要看当前所改变的时间到底改变了这些时间的那些数字,所以我们要对六个数字依次进行一次判断,对此呢我的做法如下:

1
2
3
if( parseInt(curHours/10) != parseInt(nextHours/10) ){
    addBalls( MARGIN_LEFT + 0 , MARGIN_TOP , parseInt(curHours/10) );
}

大家可以看一下,以小时的十位数为例,如果我发现当前的小时的十位数已经不等于下次要显示的这个小时十位数,我在这里创建了一个新的函数addBalls,addBalls负责加小球,那么在具体的加小球怎么加呢?我要找到这个小时的十位数所在的位置,就是MARGIN_LEFT和MARGIN_TOP这个位置,以及相应的数字是多少,这个相应的数字就是当前这个小球所显示的十位数字。那么在这个addBalls函数里呢我将根据这些状态生成一系列的小球,那么类似的大家可以想象,我对这个小时的个位数也需要进行一次这样的操作:

1
2
3
if( parseInt(curHours%10) != parseInt(nextHours%10) ){
    addBalls( MARGIN_LEFT + 15*(RADIUS+1) , MARGIN_TOP , parseInt(curHours/10) );
}

注意这里的位置是MARGIN_LEFT + 15*(RADIUS+1)不知道大家还记不记得为什么是15倍的(RADIUS+1),在之前的文章中我们分析过。同时我们需要对时间的分钟进行这样的操作,时间的秒钟也需要进行这样的操作。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
if( parseInt(curMinutes/10) != parseInt(nextMinutes/10) ){
    addBalls( MARGIN_LEFT + 39*(RADIUS+1) , MARGIN_TOP , parseInt(curMinutes/10) );
}
 if( parseInt(curMinutes%10) != parseInt(nextMinutes%10) ){
    addBalls( MARGIN_LEFT + 54*(RADIUS+1) , MARGIN_TOP , parseInt(curMinutes%10) );
}

if( parseInt(curSeconds/10) != parseInt(nextSeconds/10) ){
    addBalls( MARGIN_LEFT + 78*(RADIUS+1) , MARGIN_TOP , parseInt(curSeconds/10) );
}
if( parseInt(curSeconds%10) != parseInt(nextSeconds%10) ){
    addBalls( MARGIN_LEFT + 93*(RADIUS+1) , MARGIN_TOP , parseInt(nextSeconds%10) );
}

至此问题的关键就是addBalls这个函数应该怎么实现,这这里大家可以想象到这个函数跟我们之前设计的renderDigit函数会非常的像,renderDigit函数是在x、y的位置对num数字点阵化进行渲染,那addBalls则是在x、y的位置对num这个数字点阵化位置加上一个彩色的小球,那么这个函数大家可以想象一下也是进行一次二重循环,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function addBalls( x , y , num ){

    for( var i = 0  ; i < digit[num].length ; i ++ )
        for( var j = 0  ; j < digit[num][i].length ; j ++ )
            if( digit[num][i][j] == 1 ){
                var aBall = {
                    x:x+j*2*(RADIUS+1)+(RADIUS+1),
                    y:y+i*2*(RADIUS+1)+(RADIUS+1),
                    g:1.5+Math.random(),
                    vx:Math.pow( -1 , Math.ceil( Math.random()*1000 ) ) * 4,
                    vy:-5,
                    color: colors[ Math.floor( Math.random()*colors.length ) ]
                }

                balls.push( aBall )
            }
}

第一重循环对digit[num][i].length进行一次循环,第二重循环对digit[num][i].length进行一次循环,在两重循环之后我们应该判断一下digit[num][i][j]是否为1,如果为1我们就应该在这个位置添加一个小球,具体的添加过程,首先我们建一个aBall即一个小球,这样一个类的对象,包含坐标位置x,x位置在前我们分析过了是这样一个位置x+j2(RADIUS+1)+(RADIUS+1),同理,我们添加这个小球y位置y位置也在之前的文章课程中分析过是这样的一个位置y+i2(RADIUS+1)+(RADIUS+1),之后小球应该有一个半径,那么在这里呢我们都用我们声明的全局变量RADIUS表示了,所以在这个小球的信息里可以不写它了。接下来就是小球运动相关的信息,首先我们设置一下它的加速度,我是用这样一个式子1.5加上0~1之间随机数字:1.5+Math.random(),这样小球的加速度呢就是在1.5到2.5之间,我产生这样一个变化呢就是让每个小球稍微不同,这样看起来表现更加活泼,之后就是小球在x轴方向的速度,我采用这样的式子:Math.pow( -1 , Math.ceil( Math.random()*1000 ) ) * 4,这个式子看起来有点复杂,仔细分析一下很简单,就是-1的多少次方,这个多少次方我采用0~1之间随机数乘以1000,换句话说,在0到1000之间用ceil方式取整,所以这段式子所表达的意思就是取负1或者是正1,如果我们随机出来这个结果是偶数,那么结果为正1,若果是奇数则结果为负1,最后乘以4,所以这个小球在水平方向x轴方向的速度是取负4或者是正4的。当然对于这个结果大家可以用更复杂的随机机制让这个结果更加随机化,在这里我只是举一个例子。那同样在y方向的速度为了简单起见我起了一个固定的值负5,负5这个值呢会使所有小球在蹦出来的时候有一个稍微向上抛的这么一个动作。同样大家可以使用随机化手段让每一个小球速度稍有不同,这样表现出来的结果更加灵活。最后一个元素呢是这一个小球特有的color值,之前我建立了一个colors数组,在这个数组里我存了10个颜色,为此我要随机一个索引,随机这个索引的方法就是用Math.random这个函数取一个0到1的随机数之后乘以colors.length,然后用下取整的方式,这样随机出0到10 不包含10的随机数。

在这个方法里面我用了很多随机化的概念,使的每一个小球稍有不同,这样表现起来更加灵活。同时大家还可以改造这些式子,让自己的小球拥有更随机化的效果,使得小球表现的结果更加灵活。

这样呢我,我们就创建好了一个小球,之后我们在这个balls数据中push()把我们刚刚建立的这个小球push进去,通过这样的一个循环过程,我们就在合适的位置产生了这些小球。我们新产生的这些小球都是静止的,那么为了让它们运动起来我们还需要编写相应的方法,这个方法呢还应该放在update函数里,这个update函数负责了时间的改变,负责了如果当前需要产生新的小球那么就要产生新的小球这样变化,与此同时还需要负责对已经产生的小球运动变化进行更新,这里面我们声明一个新的函数updateBalls,这个函数对所有已经存在小球的状态进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function updateBalls(){
    //对balls的数组进行遍历
    for( var i = 0 ; i < balls.length ; i ++ ){
        //x轴坐标的位置加上他在x轴方向的速度值
        balls[i].x += balls[i].vx;
        //y轴坐标的位置加上他在y轴方向的速度值
        balls[i].y += balls[i].vy;
        //y的速度受重力的影响
        balls[i].vy += balls[i].g;
        //对地板的部分进行一次碰撞检测
        if( balls[i].y >= WINDOW_HEIGHT-RADIUS ){
            balls[i].y = WINDOW_HEIGHT-RADIUS;
            //y轴的速度取一个相反的速度
            balls[i].vy = - balls[i].vy*0.75;
        }
    }
}

上面的代码是对所有的小球更新进行操作,所以首先我们要进行一层循环,对balls的数组进行遍历,那么每一次就取得了一个小球,对于这一个小球我们对它的位置进行相应的变化,首先是x轴坐标的位置加上他在x轴方向的速度值,y轴坐标的位置加上他在y轴方向的速度值,由于小球重力的影响,y的速度受重力的影响进行这样一个操作。在上面我们也提到了应该对地板的部分进行一次碰撞检测,如果当前小球的y坐标的位置已经大于等于屏幕的高度减去小球半径值的话,那么Y坐标位置复原到地板上的位置,同时y轴的速度取一个相反的速度,这样小球的基本运动就完成了。

最后就剩下处理小球的绘制了,相信大家对小球的绘制非常熟悉了,我们找到render这个函数,在后面我们加上一段代码对小球的数组进行一次遍历,之后对每一个小球进行具体绘制,具体代码如下:

1
2
3
4
5
6
7
8
9
for( var i = 0 ; i < balls.length ; i ++ ){
        cxt.fillStyle=balls[i].color;

        cxt.beginPath();
        cxt.arc( balls[i].x , balls[i].y , RADIUS , 0 , 2*Math.PI , true );
        cxt.closePath();

        cxt.fill();
}

现在我们整个倒计时小球的绘制就出来,下面我们来看看这个程序运行的结果:

canvas实现绚丽的倒计时效果与动画基础(二)

是不是非常的炫酷,在本章内容中对小球的运动进行了诸多的设置,大家也可以通过自己的想法进行改造,比如说碰撞检测可不可以对两边进行相应的碰撞检测,还有弹跳的数值可不可以改造呢,使得小球的表现方式截然不同。都有待大家去实验,相信是件非常好玩的事情,大家加油。

「除特别注明外,本站所有文章均为码云笔记原创,转载请保留出处!」

赞(6) 打赏

觉得文章有用就打赏一下文章作者

支付宝
微信
6

觉得文章有用就打赏一下文章作者

支付宝
微信

上一篇:

下一篇:

你可能感兴趣

共有 2 条评论 - canvas实现绚丽的倒计时效果与动画基础(二)

  1. 小懒鸟 Linux Chrome 62.0.3202.84

    不错不错,思路很好,大佬有源码吗?分享一下,哈哈

    1. 码云 Windows 7 Chrome 63.0.3239.132

      @小懒鸟有的,在第三篇文章,马上发布

博客简介

码云笔记 mybj123.com,一个专注Web前端开发技术的博客,主要记录和总结博主在前端开发工作中常用的实战技能及前端资源分享,分享各种科普知识和实用优秀的代码,以及分享些热门的互联网资讯和福利!码云笔记有你更精彩!
更多博客详情请看关于博客

精彩评论

站点统计

  • 文章总数: 472 篇
  • 分类数目: 13 个
  • 独立页面: 8 个
  • 评论总数: 228 条
  • 链接总数: 15 个
  • 标签总数: 1040 个
  • 建站时间: 522 天
  • 访问总量: 8681397 次
  • 最近更新: 2019年11月18日