JavaScript動(dòng)畫(huà)基礎(chǔ):canvas繪制簡(jiǎn)單動(dòng)畫(huà)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谣蠢,隨后出現(xiàn)的幾起案子粟耻,更是在濱河造成了極大的恐慌,老刑警劉巖眉踱,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挤忙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谈喳,警方通過(guò)查閱死者的電腦和手機(jī)册烈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)婿禽,“玉大人赏僧,你說(shuō)我怎么就攤上這事∨で悖” “怎么了淀零?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)膛壹。 經(jīng)常有香客問(wèn)我驾中,道長(zhǎng),這世上最難降的妖魔是什么模聋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任肩民,我火速辦了婚禮,結(jié)果婚禮上链方,老公的妹妹穿的比我還像新娘此改。我一直安慰自己,他們只是感情好侄柔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布共啃。 她就那樣靜靜地躺著占调,像睡著了一般。 火紅的嫁衣襯著肌膚如雪移剪。 梳的紋絲不亂的頭發(fā)上究珊,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音纵苛,去河邊找鬼剿涮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛攻人,可吹牛的內(nèi)容都是我干的取试。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怀吻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瞬浓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蓬坡,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤猿棉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后屑咳,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體萨赁,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年兆龙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杖爽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡紫皇,死狀恐怖掂林,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坝橡,我是刑警寧澤泻帮,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站计寇,受9級(jí)特大地震影響锣杂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜番宁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一元莫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝶押,春花似錦踱蠢、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)苇侵。三九已至,卻和暖如春企锌,著一層夾襖步出監(jiān)牢的瞬間榆浓,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工撕攒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陡鹃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓抖坪,卻偏偏與公主長(zhǎng)得像萍鲸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子擦俐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355