JavaScript动画基础:canvas绘制简单动画 | 您所在的位置:网站首页 › 如何用html制作一个动画效果 › JavaScript动画基础:canvas绘制简单动画 |
动画是将静止的画面变为动态的艺术.实现由静止到动态,主要是靠人眼的视觉残留效应。视觉残留也叫视觉暂留现象,物体在快速运动时, 当人眼所看到的影像消失后,人眼仍能继续保留其影像0.1~0.4秒左右的图像,这种现象被称为视觉暂留现象。利用人的这种视觉生理特性可制作出具有高度想象力和表现力的动画影片。 电影的拍摄和放映就是视觉残留效应的具体应用。 大家可能看过组成电影的实际胶片。从表面上看,它们像一堆画面串在一条塑料胶片上。每一个画面称为一帧,代表电影中的一个时间片段。这些帧的内容总比前一帧有稍微的变化,这样,当电影胶片在投影机上放映时就产生了运动的错觉:每一帧都很短并且很快被另一个帧所代替,这样就产生了运动。 通过循环绘制各帧的图像就可以实现动画的效果。 在Canvas画布中制作动画相对来说很简单,实际上就是绘制帧(图形或图像)、擦除、重绘的过程。也就是说,在Canvas中模拟一个动画过程就是每隔一定时间绘制图形并且清除图形,通过定时循环操作实现。 1.定时循环操作的三个函数对于动画,需要在一段时间内渲染不同的帧,各帧间隔一定的时间在画布中依次被绘制。为完成定时循环操作帧,可以利用etInterval()、setTimeout()和requestAnimationFrame()这三个函数之一。 (1)setTimeout()方法。 setTimeout() 方法是HTML DOM Window对象的一个方法,它用于在指定的毫秒数后调用函数或计算表达式。其调用格式为: setTimeout(code,millisec); 其中,参数code表示要调用的函数或要执行的代码串,millisec表示在执行代码前需等待的毫秒数。 例如,setTimeout(“draw()”,1000)表示延时1秒后执行函数draw中的代码。 编写如下的HTML文件。 setTimeout方法的应用 var canvas = document.getElementById('myCanvas'); var ctx = canvas.getContext('2d'); function draw(x,y,len,color) { ctx.fillStyle = color; ctx.fillRect(x,y,len,len); } setTimeout("draw(10,10,100,'red')",1000); setTimeout("draw(110,110,200,'blue')",5000); 在浏览器中打开保存这段HTML代码的html文件,则等待1秒后,会绘制一个边长为100的红色正方形,再等待5秒,绘制一个边长为200的蓝色正方形。 通过这个例子可以知道:(1)setTimeout()方法可以用于延时;(2)setTimeout()方法只执行code一次。如果要多次调用,则需要让code 自身再次调用 setTimeout()。 为产生动画效果,显然得让setTimeout()方法多次执行。修改上面的HTML代码如下。 setTimeout方法的应用 var canvas = document.getElementById('myCanvas'); var ctx = canvas.getContext('2d'); var i=0; function move() { ctx.fillStyle = 'red'; ctx.fillRect(i,i,50,50); i++; if (i==350) { i=0; ctx.clearRect(0,0,400,400); } setTimeout("move()",10); } move(); 在浏览器中打开包含这段HTML代码的html文件,可以在浏览器窗口中看到一个简单的箭头伸出动画,如图1所示。
图1 简单的动画 (2)setInterval() 方法。 setInterval()也是HTML DOM Window对象的一个方法,它可按照指定的周期(以毫秒计)来调用函数或计算表达式。其调用格式为: setInterval(code,millisec); 其中,参数code表示要调用的函数或要执行的代码串, millisec表示周期性执行或调用 code 之间的时间间隔(以毫秒计)。 setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。 clearInterval() 方法可取消由 setInterval() 设置的 timeout。其调用形式为: clearInterval(id_of_setinterval); 其中参数id_of_setinterval必须是由 setInterval() 返回的 ID 值。 若用setInterval() 方法实现图1所示的动画,则编写的HTML文件如下。 setInterval()方法的应用 var canvas = document.getElementById('myCanvas'); var ctx = canvas.getContext('2d'); var i=0; function move() { ctx.fillStyle = 'red'; ctx.fillRect(i,i,50,50); i++; if (i==350) { i=0; ctx.clearRect(0,0,400,400); } } setInterval("move()",10); (3)requestAnimationFrame()方法。 requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。 编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能保证不同的动画效果显得更平滑流畅;另一方面,循环间隔还要足够长,这样才能保证浏览器有能力渲染产生的变化。大多数显示器的刷新频率是60Hz,相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过了这个频率,用户体验也不会有提升。 因此,最平滑动画的最佳循环间隔是1000ms/60,约等于17ms。以这个循环间隔重绘的动画是平滑的,因为这个速度最接近浏览器的最高限速。为了适应17ms的循环间隔,多重动画可能需要加以节制,以便不会完成得太快。 虽然setTimeout()方法和setInterval()方法均可完成定时循环操作,但setTimeout()和setInterval() 都不十分精确。为它们传入的第二个参数millisec,实际上只是指定了把动画代码添加到浏览器UI线程队列以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务执行完成后再执行。如果UI线程繁忙,比如忙于处理用户操作,那么即使把代码加入队列也不会立即执行。 确定什么时候绘制下一帧是保证动画平滑的关键。然而,面对不十分精确的 setTimeout()和setInterval(),开发人员至今都没有办法确保浏览器按时绘制下一帧。因此,采用setTimeout()和setInterval(),即使优化了循环间隔,可能仍然只能接近想要的效果。 引入requestAnimationFrame()方法的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。代码中使用requestAnimationFrame()方法,就是告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。 requestAnimationFrame的优势在于充分利用显示器的刷新机制,比较节省系统资源。显示器有固定的刷新频率(60Hz或75Hz),也就是说,每秒最多只能重绘60次或75次,requestAnimationFrame的基本思想就是与这个刷新频率保持同步,利用这个刷新频率进行页面重绘。 不过有一点需要注意,requestAnimationFrame是在主线程上完成。这意味着,如果主线程非常繁忙,requestAnimationFrame的动画效果会大打折扣。 requestAnimationFrame使用一个回调函数作为参数。这个回调函数会在浏览器重绘之前调用。其调用格式为: requestID = window.requestAnimationFrame(callback); 目前,主流浏览器(Firefox 23 / IE 10 / Chrome / Safari)都支持这个方法。可以用下面的方法,检查浏览器是否支持requestAnimationFrame。如果不支持,则自行模拟部署该方法。 window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })(); 上面的代码按照1秒钟60次(大约每16.7毫秒一次),来模拟requestAnimationFrame。 与 setTimeout() 和 setInterval() 方法不同,requestAnimationFrame( )不需要调用者指定帧速率,浏览器会自行决定最佳的帧效率。也就是说浏览器页面每次要重绘,就会通知requestAnimationFrame。如果浏览器绘制间隔是16.7ms,它就按这个间隔绘制;如果浏览器绘制间隔是10ms,它就按10ms绘制。这样就不会存在过度绘制的问题,动画不会丢帧。 另外,使用requestAnimationFrame()方法,一旦页面不处于浏览器的当前标签,就会自动停止刷新。例如,页面最小化了,页面是不会进行重绘的,requestAnimationFrame自然也不会触发(因为没有通知)。页面绘制全部停止,资源高效利用,节省了CPU、GPU和电力。 和setTimeout类似,requestAnimationFrame的回调函数只能被调用一次,并不能被重复调用(这点和setInterval不同)。因此,使用requestAnimationFrame的时候,同样需要反复调用它。 由于setTimeout可以自定义调用时间, requestAnimationFrame的调用时间则是跟着系统的刷新频率走的,所以在实现动画的时候,setTimeout比requestAnimationFrame更加灵活, requestAnimationFrame比setTimeout表现效果更加优秀。 若用requestAnimationFrame() 方法实现图1所示的动画,则编写的HTML文件如下。 requestAnimationFrame方法的应用 var canvas = document.getElementById('myCanvas'); var ctx = canvas.getContext('2d'); var i=0; function move() { ctx.fillStyle = 'blue'; ctx.fillRect(i,i,50,50); i++; if (i==350) { i=0; ctx.clearRect(0,0,400,400); } requestAnimationFrame(move); } move(); 2.绘制简单图形实现动画图1的动画就是从左上角坐标位置(0,0)开始,绘制一个边长为50的红色正方形,之后每隔10毫秒后将左上角坐标位置的水平和垂直坐标均增加1,再绘制一个正方形,从而得到一个简单的箭头伸出动画效果。 通过在画布中绘制简单图形,达到时间间隔后,擦除(有时候也可暂时不擦除)前次绘制的图形,重新绘制一个位置或大小略有变化的图形,这样就可得到动画效果。 例1 向中心交汇的箭头。 仿照图1动画思想略作变化,编写如下的HTML代码。 向中心交汇的箭头 var i=0; function draw(id) { var canvas = document.getElementById(id); ctx = canvas.getContext('2d'); setInterval(painting,10); } function painting() { ctx.fillStyle = "green"; ctx.fillRect(i,i,10,10); ctx.fillRect(400-i,400-i,10,10); ctx.fillRect(i,400-i,10,10); ctx.fillRect(400-i,i,10,10); i++; if (i==200) { ctx.clearRect(0,0,400,400); i=0; } } 在浏览器中打开包含这段HTML代码的html文件,可以在浏览器窗口中看到如图2所示的动画。
图2 向中心交汇的箭头 例2 逐层向里绘制的圆。 层层向内画的圆 var canvas = document.getElementById('myCanvas'); var context = canvas.getContext('2d'); var flag=1; var i=0; var r=180; function animate() { window.requestAnimationFrame(animate); draw(); } function draw() { var dig=Math.PI/120; var x = Math.sin(i*dig)*r+200; var y = Math.cos(i*dig)*r+200; context.fillStyle = flag ? 'rgb(10,255,255)' : 'rgb(255,100,0)'; context.beginPath(); context.arc(x, y, 3, 0, Math.PI*2, true); context.closePath(); context.fill(); i++; if (i>240) { i=0; r=r-20; flag = !flag; if (r |
CopyRight 2018-2019 实验室设备网 版权所有 |