1.定時(shí)循環(huán)操作的三個(gè)函數(shù)
? ? ? 對(duì)于動(dòng)畫(huà)撕彤,需要在一段時(shí)間內(nèi)渲染不同的幀,各幀間隔一定的時(shí)間在畫(huà)布中依次被繪制。為完成定時(shí)循環(huán)操作幀羹铅,可以利用etInterval()蚀狰、setTimeout()和requestAnimationFrame()這三個(gè)函數(shù)之一。
? ? ? (1)setTimeout()方法职员。
? ? ? setTimeout() 方法是HTML DOM Window對(duì)象的一個(gè)方法麻蹋,它用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式。其調(diào)用格式為:
? ? ? setTimeout(code,millisec);
? ? ? 其中焊切,參數(shù)code表示要調(diào)用的函數(shù)或要執(zhí)行的代碼串扮授,millisec表示在執(zhí)行代碼前需等待的毫秒數(shù)。
例如专肪,setTimeout(“draw()”,1000)表示延時(shí)1秒后執(zhí)行函數(shù)draw中的代碼刹勃。
編寫(xiě)如下的HTML文件。
<!DOCTYPE html>
<html>
<head>
<title>setTimeout方法的應(yīng)用</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>
? ? ? 在瀏覽器中打開(kāi)保存這段HTML代碼的html文件嚎尤,則等待1秒后荔仁,會(huì)繪制一個(gè)邊長(zhǎng)為100的紅色正方形,再等待5秒芽死,繪制一個(gè)邊長(zhǎng)為200的藍(lán)色正方形乏梁。
? ? ? 通過(guò)這個(gè)例子可以知道:(1)setTimeout()方法可以用于延時(shí);(2)setTimeout()方法只執(zhí)行code一次关贵。如果要多次調(diào)用遇骑,則需要讓code 自身再次調(diào)用 setTimeout()。
? ? ? 為產(chǎn)生動(dòng)畫(huà)效果坪哄,顯然得讓setTimeout()方法多次執(zhí)行质蕉。修改上面的HTML代碼如下。
<!DOCTYPE html>
<html>
<head>
<title>setTimeout方法的應(yīng)用</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>
? ? ? 在瀏覽器中打開(kāi)包含這段HTML代碼的html文件翩肌,可以在瀏覽器窗口中看到一個(gè)簡(jiǎn)單的箭頭伸出動(dòng)畫(huà)模暗,如圖1所示。
圖1? 簡(jiǎn)單的動(dòng)畫(huà)
? ? ? (2)setInterval() 方法念祭。
? ? ? setInterval()也是HTML DOM Window對(duì)象的一個(gè)方法兑宇,它可按照指定的周期(以毫秒計(jì))來(lái)調(diào)用函數(shù)或計(jì)算表達(dá)式。其調(diào)用格式為:
? ? ? setInterval(code,millisec);
? ? ? 其中粱坤,參數(shù)code表示要調(diào)用的函數(shù)或要執(zhí)行的代碼串隶糕, millisec表示周期性執(zhí)行或調(diào)用 code 之間的時(shí)間間隔(以毫秒計(jì))。
? ? ? setInterval() 方法會(huì)不停地調(diào)用函數(shù)站玄,直到 clearInterval() 被調(diào)用或窗口被關(guān)閉枚驻。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的參數(shù)。
? ? ? clearInterval() 方法可取消由 setInterval() 設(shè)置的 timeout株旷。其調(diào)用形式為:
? ? ? ? clearInterval(id_of_setinterval);
? ? ? 其中參數(shù)id_of_setinterval必須是由 setInterval() 返回的 ID 值再登。
? ? ? 若用setInterval() 方法實(shí)現(xiàn)圖1所示的動(dòng)畫(huà)尔邓,則編寫(xiě)的HTML文件如下。
<!DOCTYPE html>
<html>
<head>
<title>setInterval()方法的應(yīng)用</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是瀏覽器用于定時(shí)循環(huán)操作的一個(gè)接口梯嗽,類(lèi)似于setTimeout,主要用途是按幀對(duì)網(wǎng)頁(yè)進(jìn)行重繪沽损。
? ? ? 編寫(xiě)動(dòng)畫(huà)循環(huán)的關(guān)鍵是要知道延遲時(shí)間多長(zhǎng)合適灯节。一方面,循環(huán)間隔必須足夠短绵估,這樣才能保證不同的動(dòng)畫(huà)效果顯得更平滑流暢炎疆;另一方面,循環(huán)間隔還要足夠長(zhǎng)国裳,這樣才能保證瀏覽器有能力渲染產(chǎn)生的變化磷雇。大多數(shù)顯示器的刷新頻率是60Hz,相當(dāng)于每秒鐘重繪60次躏救。大多數(shù)瀏覽器都會(huì)對(duì)重繪操作加以限制唯笙,不超過(guò)顯示器的重繪頻率,因?yàn)榧词钩^(guò)了這個(gè)頻率盒使,用戶(hù)體驗(yàn)也不會(huì)有提升崩掘。
? ? ? 因此,最平滑動(dòng)畫(huà)的最佳循環(huán)間隔是1000ms/60少办,約等于17ms苞慢。以這個(gè)循環(huán)間隔重繪的動(dòng)畫(huà)是平滑的,因?yàn)檫@個(gè)速度最接近瀏覽器的最高限速英妓。為了適應(yīng)17ms的循環(huán)間隔挽放,多重動(dòng)畫(huà)可能需要加以節(jié)制,以便不會(huì)完成得太快蔓纠。
? ? ? 雖然setTimeout()方法和setInterval()方法均可完成定時(shí)循環(huán)操作辑畦,但setTimeout()和setInterval() 都不十分精確。為它們傳入的第二個(gè)參數(shù)millisec腿倚,實(shí)際上只是指定了把動(dòng)畫(huà)代碼添加到瀏覽器UI線(xiàn)程隊(duì)列以等待執(zhí)行的時(shí)間纯出。如果隊(duì)列前面已經(jīng)加入了其他任務(wù),那動(dòng)畫(huà)代碼就要等前面的任務(wù)執(zhí)行完成后再執(zhí)行敷燎。如果UI線(xiàn)程繁忙暂筝,比如忙于處理用戶(hù)操作,那么即使把代碼加入隊(duì)列也不會(huì)立即執(zhí)行硬贯。
? ? ? 確定什么時(shí)候繪制下一幀是保證動(dòng)畫(huà)平滑的關(guān)鍵焕襟。然而,面對(duì)不十分精確的 setTimeout()和setInterval()饭豹,開(kāi)發(fā)人員至今都沒(méi)有辦法確保瀏覽器按時(shí)繪制下一幀鸵赖。因此畏吓,采用setTimeout()和setInterval(),即使優(yōu)化了循環(huán)間隔卫漫,可能仍然只能接近想要的效果。
? ? ? 引入requestAnimationFrame()方法的目的是為了讓各種網(wǎng)頁(yè)動(dòng)畫(huà)效果(DOM動(dòng)畫(huà)肾砂、Canvas動(dòng)畫(huà)列赎、SVG動(dòng)畫(huà)、WebGL動(dòng)畫(huà))能夠有一個(gè)統(tǒng)一的刷新機(jī)制镐确,從而節(jié)省系統(tǒng)資源包吝,提高系統(tǒng)性能,改善視覺(jué)效果源葫。代碼中使用requestAnimationFrame()方法诗越,就是告訴瀏覽器希望執(zhí)行一個(gè)動(dòng)畫(huà),讓瀏覽器在下一個(gè)動(dòng)畫(huà)幀安排一次網(wǎng)頁(yè)重繪息堂。
? ? ? requestAnimationFrame的優(yōu)勢(shì)在于充分利用顯示器的刷新機(jī)制嚷狞,比較節(jié)省系統(tǒng)資源。顯示器有固定的刷新頻率(60Hz或75Hz)荣堰,也就是說(shuō)床未,每秒最多只能重繪60次或75次,requestAnimationFrame的基本思想就是與這個(gè)刷新頻率保持同步振坚,利用這個(gè)刷新頻率進(jìn)行頁(yè)面重繪薇搁。
? ? ? 不過(guò)有一點(diǎn)需要注意,requestAnimationFrame是在主線(xiàn)程上完成渡八。這意味著啃洋,如果主線(xiàn)程非常繁忙,requestAnimationFrame的動(dòng)畫(huà)效果會(huì)大打折扣屎鳍。
? ? ? requestAnimationFrame使用一個(gè)回調(diào)函數(shù)作為參數(shù)宏娄。這個(gè)回調(diào)函數(shù)會(huì)在瀏覽器重繪之前調(diào)用。其調(diào)用格式為:
? ? ? requestID = window.requestAnimationFrame(callback);
? ? ? 目前逮壁,主流瀏覽器(Firefox 23 / IE 10 / Chrome / Safari)都支持這個(gè)方法绝编。可以用下面的方法貌踏,檢查瀏覽器是否支持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毫秒一次)逗堵,來(lái)模擬requestAnimationFrame。
? ? ? 與 setTimeout() 和 setInterval() 方法不同眷昆,requestAnimationFrame( )不需要調(diào)用者指定幀速率蜒秤,瀏覽器會(huì)自行決定最佳的幀效率汁咏。也就是說(shuō)瀏覽器頁(yè)面每次要重繪,就會(huì)通知requestAnimationFrame作媚。如果瀏覽器繪制間隔是16.7ms攘滩,它就按這個(gè)間隔繪制;如果瀏覽器繪制間隔是10ms纸泡,它就按10ms繪制漂问。這樣就不會(huì)存在過(guò)度繪制的問(wèn)題,動(dòng)畫(huà)不會(huì)丟幀女揭。
? ? ? 另外蚤假,使用requestAnimationFrame()方法,一旦頁(yè)面不處于瀏覽器的當(dāng)前標(biāo)簽吧兔,就會(huì)自動(dòng)停止刷新磷仰。例如,頁(yè)面最小化了境蔼,頁(yè)面是不會(huì)進(jìn)行重繪的灶平,requestAnimationFrame自然也不會(huì)觸發(fā)(因?yàn)闆](méi)有通知)。頁(yè)面繪制全部停止箍土,資源高效利用民逼,節(jié)省了CPU、GPU和電力涮帘。
? ? ? 和setTimeout類(lèi)似拼苍,requestAnimationFrame的回調(diào)函數(shù)只能被調(diào)用一次,并不能被重復(fù)調(diào)用(這點(diǎn)和setInterval不同)调缨。因此疮鲫,使用requestAnimationFrame的時(shí)候,同樣需要反復(fù)調(diào)用它弦叶。
? ? ? 由于setTimeout可以自定義調(diào)用時(shí)間俊犯, requestAnimationFrame的調(diào)用時(shí)間則是跟著系統(tǒng)的刷新頻率走的,所以在實(shí)現(xiàn)動(dòng)畫(huà)的時(shí)候伤哺,setTimeout比requestAnimationFrame更加靈活燕侠, requestAnimationFrame比setTimeout表現(xiàn)效果更加優(yōu)秀。
? ? ? 若用requestAnimationFrame() 方法實(shí)現(xiàn)圖1所示的動(dòng)畫(huà)立莉,則編寫(xiě)的HTML文件如下绢彤。
<!DOCTYPE html>
<html>
<head>
<title>requestAnimationFrame方法的應(yīng)用</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.繪制簡(jiǎn)單圖形實(shí)現(xiàn)動(dòng)畫(huà)
? ? ? 圖1的動(dòng)畫(huà)就是從左上角坐標(biāo)位置(0,0)開(kāi)始,繪制一個(gè)邊長(zhǎng)為50的紅色正方形蜓耻,之后每隔10毫秒后將左上角坐標(biāo)位置的水平和垂直坐標(biāo)均增加1茫舶,再繪制一個(gè)正方形,從而得到一個(gè)簡(jiǎn)單的箭頭伸出動(dòng)畫(huà)效果刹淌。
? ? ? 通過(guò)在畫(huà)布中繪制簡(jiǎn)單圖形饶氏,達(dá)到時(shí)間間隔后讥耗,擦除(有時(shí)候也可暫時(shí)不擦除)前次繪制的圖形,重新繪制一個(gè)位置或大小略有變化的圖形疹启,這樣就可得到動(dòng)畫(huà)效果古程。
例1? 向中心交匯的箭頭。
仿照?qǐng)D1動(dòng)畫(huà)思想略作變化喊崖,編寫(xiě)如下的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>
? ? ? 在瀏覽器中打開(kāi)包含這段HTML代碼的html文件,可以在瀏覽器窗口中看到如圖2所示的動(dòng)畫(huà)贷祈。
圖2? 向中心交匯的箭頭
? ? ? 例2? 逐層向里繪制的圓。
<!DOCTYPE html>
<html>
<head>
<title>層層向內(nèi)畫(huà)的圓</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>
? ? ? 在瀏覽器中打開(kāi)包含這段HTML代碼的html文件喝峦,可以在瀏覽器窗口中看到如圖3所示的動(dòng)畫(huà)势誊。
龍華大道1號(hào)http://www.kinghill.cn/LongHuaDaDao1Hao/index.html