码云笔记提示:你现在浏览的网站是镜像网站

请访问原网站:www.mybj123.com
声明:码云笔记唯一指定网址为:mybj123 .com,其他网址均为冒充,望周知!!!前端技术交流群:565733884

canvas实现绚丽的倒计时效果优化与扩展(三)

JavaScript 码云 415℃ 0评论
目录
[隐藏]

经过之前的课程 《canvas实现绚丽的倒计时效果与动画基础(一)》和《canvas实现绚丽的倒计时效果与动画基础(二)》相信大家已经完全实现并掌握了倒计时效果,但是对于我们的程序还是有些问题,是可以进行优化的,或者说是更完善的。下面我将针对这些问题进行简单介绍并带领大家一起优化我们的作品。

性能优化

第一个大的问题可能大家也发现了,在我们update的过程中,我们在这个addBalls函数里不停地在往我们的canvas里增加新的小球,但是有增加却没有减少,这会导致什么情况呢?为此我在update函数里的updateBalls()下面增加了一句代码:

console.log( balls.length);

也就是在控制台打印出来balls的数组的长度,大家会看到每隔50毫秒会调用一次update函数,在update函数里就会打印出balls.length,数组元素是只增无减的,越来越多,这样的程序肯定是不能长时间运行的,每一个计算机都有它的极限,balls数组在不停的吃我们的内存,但是大家可以分析一下,当这个小球已经走出这个画面的时候,此时这个小球就可以不留在这个数组里,如果我们有个机制能够维护这个数组,让走出这个画面的小球从数组里删去的话,这样这个程序就可以长时间运行了。具体如何实现呢,我们来看一下代码:

function updateBalls(){

    for( var i = 0 ; i < balls.length ; i ++ ){

        balls[i].x += balls[i].vx;
        balls[i].y += balls[i].vy;
        balls[i].vy += balls[i].g;

        if( balls[i].y >= WINDOW_HEIGHT-RADIUS ){
            balls[i].y = WINDOW_HEIGHT-RADIUS;
            balls[i].vy = - balls[i].vy*0.75;
        }
    }

    var cnt = 0
    for( var i = 0 ; i < balls.length ; i ++ )
        if( balls[i].x + RADIUS > 0 && balls[i].x -RADIUS < WINDOW_WIDTH )
            balls[cnt++] = balls[i]

    while( balls.length > cnt ){
        balls.pop();
    }
}

我把对小球数量的控制也放到updateBalls函数中,毕竟在这我们要删去小球数量,也是对balls的一个update,思路:首先对balls数据进行一次遍历,在这个遍历中要对每一个小球的当前位置进行判断,看看它现在是否还在画面内,如何判断呢?就是这个小球的右边缘仍然大于0,那么一个小球的右边缘就是它的中心坐标的位置再加上它的半径,如果这个位置仍然比0大的话,说明这个小球一定还有一部分留在画面里,与此同时它又不能越过整个画面,那么我们为了判断这一点呢我们还要再加上一个条件,这个条件就是小球本身的左边缘一定要比整个画面的长度还要小,也就是这个小球的中心位置减去半径一定要小于WINDOW_WIDTH,这样这个小球还留在画面中我们可以把这个小球放在个数组里。具体对这个小球的保留我是用了一个小技巧,首先我声明一个变量cnt,初始化为0,这个数量将计划有多少个小球还保留在整个画布中,一旦我找到这样的小球我就就进行这样一个操作“balls[cnt++] = balls[i]”请大家想一下这样做的效果是什么样子,我们在遍历的过程当中由于并非所有的小球都满足于if语句,所以“[i]”的索引一定是大于等于“[cnt++]”的,由于cnt在不停的“++”,所以cnt是逐步的从0、1、2、3等等一直往上积累的,也就是说在这样的一个写法下一旦我们发现了符合规则的小球我们就把这样的小球挤在了前面,这样一来当整个循环结束以后,从0到cnt减1就都是留在画布的小球了。而从cnt开始一直到当前的balls.length-1这段长度里的小球就都不是在画布里的小球了,当然,在极端的情况下i和cnt正好相等,不过这样也没有关系,说明整个数组里的小球全部都留在画面中,请大家再体会一下这样的写法。

当我们完成了这一步后,我们就知道了在这个数组中前cnt个小球都是我们需要的,我们对cnt后面的小球都可以删掉,具体怎么删呢?我们可以使用这样一个循环:

while( balls.length > cnt ){
        balls.pop();
}

只要当前的balls.length比cnt还大的话,我们就pop把这个末尾数组的小球给删掉了,这样我们就成功的维护了这个数组,使得数组内的小球总是出现在画面上,减少我们的内存空间,具体实验希望大家在自己电脑上体会一下。

还值得一提的是,我们现在的动画相对比较简单,对这个小球运动位置的计算量比较低,所以我们把所有能融在画面上的小球全都渲染出来也是没问题的,但在某些情况下可能由于我们计算机的限制或者运算量的限制,我们只能保留有限个小球,这种时候我们还可以把cnt做一个改动,如下:

while( balls.length > Math.min(300,cnt) ){
        balls.pop();
}

可以看到,我现在把cnt改写成Math.min(300,cnt)的意思是这个balls.length的长度保留cut和300之间的最小值,如何理解这个意思呢?大家想象一下,如果我们算出来的cnt比300小,那我们balls里就取cnt个小球,但是如果我们算出来的cnt比300还大,比如说500,那我们就取300个小球,因为我们可能通过其他的测验知道计算300个小球的位置是当前我们计算机的极限了,当然这只是一个小技巧,说起动画的性能还是一个很大的话题,其中有额很多技术、技巧,相应的工具可以探讨,我们后续有时间在和大家一一介绍。

屏幕自适应

接下来我们探讨第二个问题即屏幕自适应的问题,在我们这个程序里做屏幕自适应其实非常简单,因为我们所有物体尺寸都是由下面变量控制的:

var WINDOW_WIDTH = 1024;
var WINDOW_HEIGHT = 768;
var RADIUS = 8;
var MARGIN_TOP = 60;
var MARGIN_LEFT = 30;

之前我们把这些变量写死,如果想做屏幕自适应呢,只需要将这些变量根据当前的屏幕状态而变化就好了,在这里我举个简单的例子。比如说我们画布的WINDOW_WIDTH和WINDOW_HEIGHT就可以用js的方法来赋值:

WINDOW_WIDTH = document.body.clientWidth
WINDOW_HEIGHT = document.body.clientHeight

这里有一个css的小技巧,如果我们这样调用body.clientWidth是得不到整个屏幕的高度,我们必须在HTML里面把这个屏幕的高度整个撑起来,为此我在body加一个style,height是100%,同时,我们在做自适应canvas中之前的display:block;margin:50px auto;这些css就不需要了,但是我们需要canvas这个元素把整个屏幕给撑起来,所以也写一个height等于100%。

接下来我们看一下MARGIN_LEFT,如果我希望这一些倒计时钟表上的文字占整个屏幕大约五分之四左右,这样两侧空白就要各占五分之一,那么左侧一侧的空白大概就要占整个屏幕宽度的十分之一,有了这样的计算,我MARGIN_LEFT就可以这样写:

MARGIN_LEFT = Math.round(WINDOW_WIDTH /10);

MARGIN_LEFT就等于我刚刚得到的WINDOW_WIDTH除以10十分之一,之后我再进行一个四舍五入取整。计算出这些之后我们就可以据此计算出我们画的每一个小球半径RADIUS了, 如下计算方法:

RADIUS = Math.round(WINDOW_WIDTH * 4 / 5 / 108)-1

解释一下上面代码,我们用WINDOW_WIDTH乘以4除以5也就是乘以一个五分之四,那我们算出的是整个时钟的这一些文字它们一共占得宽度是多少,是WINDOW_WIDTH乘以五分之四,之后这里我为什么要除以108呢,不知道大家还记不记得之前我们的那些计算,我们在render的时候计算过每个数字距离左边距的位置,如下:

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);

我们最后一个数字距离左边距是MARGIN_LEFT + 93*(RADIUS+1),我们又说过每一个数字呢我们给他占的空间呢是15个RADIUS+1,所以这些数字加起来总共是108个RADIUS+1 ,所以在这里我们得到了整个数字所花的空间以后又除以了一个108,即Math.round(WINDOW_WIDTH * 4 / 5 / 108),注意这里头我们得出的结果是半径加1,我们给它取整之后再减1,最后就计算出每个小球的半径了。

最后给MARGIN_TOP赋值是比较简单的了,因为MARGIN_TOP和前面计算的东西基本上没有关系,比如说我想要MARGIN_TOP是整个屏幕高度的五分之一,我只要这么些就好了:

MARGIN_TOP = Math.round(WINDOW_HEIGHT /5);

讲到这儿,我就把和位置相关的变量都进行了赋值,这样赋值以后我们的程序对当前的屏幕进行了一次自适应,我们来看一下效果:

倒计时屏幕自适应优化

此时程序运行起来数字更加大些了,因为它更加匹配我现在使用的大屏幕,是不是非常的酷

改进时间管理小工具

很多小伙伴在自己的电脑上运行不能正确的执行,如下效果:

改进时间管理小工具

这是为什么呢?接下来我就为大家讲解一下这个问题,同时也对这个源码进行一次改造,看看这样一个程序还能有怎样的扩展,其实只要大家仔细看了前面的课程介绍就会知道,现在的这个程序这样设计有一定的局限性,因为对于小时这个数字的显示,我只给出了两位数字的位置,也就是最多能够显示99小时,所以我的endTime超出了99个小时以外的时间就会出现bug。而另一方面,如果大家有印象的话,我们在getCurrentShowTimeSeconds函数里我们会计算结束的时间距离现在的时间的距离,如果这个距离小于0也就是说结束的时间比现在的时间还要早的话,那么就会返回0,这个返回的0就是我们为什么会在屏幕上显示00:00:00的原因,那么具体怎么改呢,很简单,我们要把endTime改成距离现在之后99个小时之内的一个时间,在这里大家注意一点javascript的Date函数在创建的时候有一个小的陷阱,以我们写的这个例子为例,我传入的参数是new Date(2018,6,11,18,47,52),那代表的是2014年6月11日18点47分52秒呢,答案是否定的,问题的关键是月份,在javascript中月份是从0开始的,依次采用0~11,这12个数字表示从1月到12月,所以6代表的是7月份,很多小伙伴修改了endTime最后还是失败,就是因为这个月份有问题,没有从0开始计算。在这里我举个例子,我现在写这篇文章的时间是2018年12月21日,如果我想设置倒计时的时间到计时到2018年12月22日18点47分52秒,在修改上月份就应该填11,因为11代表的是12月,而日子呢就要填22,这样修改就好了。

另外有的小伙伴认为这样每次修改endTime很麻烦的,那么有没有什么方式可以自动的让endTime就距离现在的时间之后比如之后的一个小时呢,答案是肯定的,想要修改这个代码只需要对javascript的Date对象有一定的了解即可,我们一起来看一下,此时endTime还是Date对象,但是不需要我们指定这个具体的时间了,所以我们直接使用new Date()来声明这个对象,这样一来endTime其实此时指定的是当前时间,如果想设定它成为当前之后一个小时这个时间呢,在具体使用时需要调用endTime的setTime()方法,那么setTime()方法传入一个数值,这个数值表示的是从1970年1月1日开始的一个毫秒数,就是所谓的时间戳的概念。那么用这样一个时间戳表示endTime这个时间,那么具体设置时这个时间戳设置多少呢?也就是endTime当前的毫秒数时间getTime()方法调用一下,再加上一个小时的毫秒数即3600*1000,这样的话,endTime设置就是距离当前的这个时间再向后推一个小时,这里大家要注意endTime还是要声明称var而不是const,这样就可以实现从一个小时开始倒计时。

var endTime = new Date();
endTime.setTime(endTime.getTime() + 3600*1000);

这个程序呢其实就可以作为一个时间管理的工具,比如说大家希望没一个小时都进行工作之后进行休息,如果大家愿意还可以设置成一个提醒,播放一段音乐提醒大家已经学习一个小时了。当然了大家还是要根据自己的情景来使用了。

具体源码请到git码云下载

转载请注明:码云笔记 » canvas实现绚丽的倒计时效果优化与扩展(三)

喜欢 (5)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(1)个小伙伴在吐槽
  1. 很不错的倒计时。
    潇潇尓2019-01-06 23:05 回复