1.定时循环操作的三个函数
对于动画,需要在一段时间内渲染不同的帧,各帧间隔一定的时间在画布中依次被绘制。为完成定时循环操作帧,可以利用etInterval()、setTimeout()和requestAnimationFrame()这三个函数之一。
(1)setTimeout()方法。
setTimeout() 方法是HTML DOM Window对象的一个方法,它用于在指定的毫秒数后调用函数或计算表达式。其调用格式为:
setTimeout(code,millisec);
其中,参数code表示要调用的函数或要执行的代码串,millisec表示在执行代码前需等待的毫秒数。
例如,setTimeout(“draw()”,1000)表示延时1秒后执行函数draw中的代码。
编写如下的HTML文件。
<!DOCTYPE html>
<html>
<head>
<title>setTimeout方法的应用</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">
</canvas>
<script type="text/javascript">
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);
</script>
</body>
</html>
在浏览器中打开保存这段HTML代码的html文件,则等待1秒后,会绘制一个边长为100的红色正方形,再等待5秒,绘制一个边长为200的蓝色正方形。
通过这个例子可以知道:(1)setTimeout()方法可以用于延时;(2)setTimeout()方法只执行code一次。如果要多次调用,则需要让code 自身再次调用 setTimeout()。
为产生动画效果,显然得让setTimeout()方法多次执行。修改上面的HTML代码如下。
<!DOCTYPE html>
<html>
<head>
<title>setTimeout方法的应用</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">
</canvas>
<script type="text/javascript">
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();
</script>
</body>
</html>
在浏览器中打开包含这段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文件如下。
<!DOCTYPE html>
<html>
<head>
<title>setInterval()方法的应用</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">
</canvas>
<script type="text/javascript">
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);
</script>
</body>
</html>
(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文件如下。
<!DOCTYPE html>
<html>
<head>
<title>requestAnimationFrame方法的应用</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">
</canvas>
<script type="text/javascript">
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();
</script>
</body>
</html>
2.绘制简单图形实现动画
图1的动画就是从左上角坐标位置(0,0)开始,绘制一个边长为50的红色正方形,之后每隔10毫秒后将左上角坐标位置的水平和垂直坐标均增加1,再绘制一个正方形,从而得到一个简单的箭头伸出动画效果。
通过在画布中绘制简单图形,达到时间间隔后,擦除(有时候也可暂时不擦除)前次绘制的图形,重新绘制一个位置或大小略有变化的图形,这样就可得到动画效果。
例1 向中心交汇的箭头。
仿照图1动画思想略作变化,编写如下的HTML代码。
<!DOCTYPE html>
<html>
<head>
<title>向中心交汇的箭头</title>
<script type="text/javascript">
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;
}
}
</script>
</head>
<body onload="draw('myCanvas')">
<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">
</canvas>
</body>
</html>
在浏览器中打开包含这段HTML代码的html文件,可以在浏览器窗口中看到如图2所示的动画。
图2 向中心交汇的箭头
例2 逐层向里绘制的圆。
<!DOCTYPE html>
<html>
<head>
<title>层层向内画的圆</title>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;"></canvas>
<script type="text/javascript">
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<=0) {
context.clearRect(0,0,canvas.width,canvas.height);
r=180;
}
}
}
animate();
</script>
</body>
</html>
在浏览器中打开包含这段HTML代码的html文件,可以在浏览器窗口中看到如图3所示的动画。
龙华大道1号http://www.kinghill.cn/LongHuaDaDao1Hao/index.html