動畫是將靜止的畫面變為動態的藝術.實現由靜止到動態,主要是靠人眼的視覺殘留效應。視覺殘留也叫視覺暫留現象,物體在快速運動時, 當人眼所看到的影像消失後,人眼仍能繼續保留其影像0.1~0.4秒左右的圖像,這種現象被稱為視覺暫留現象。利用人的這種視覺生理特性可製作出具有高度想象力和表現力的動畫影片。 電 ...
動畫是將靜止的畫面變為動態的藝術.實現由靜止到動態,主要是靠人眼的視覺殘留效應。視覺殘留也叫視覺暫留現象,物體在快速運動時, 當人眼所看到的影像消失後,人眼仍能繼續保留其影像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文件。
<!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所示的動畫。
圖3 層層向內畫的圓
3.通過圖形變換實現動畫效果
在Canvas中,可以繪製一個基本圖形,然後通過對這個基本圖形使用平移、縮放和旋轉等圖形變換的方法實現動畫效果。
例3 放大縮小的五角星。
<!DOCTYPE html>
<html>
<head>
<title>放大縮小的五角星</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 x=200;
var y=200;
var radius=30;
var rot=0;
var dr=5;
function draw()
{
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.translate(x,y);
ctx.rotate(rot/180*Math.PI);
ctx.scale(radius,radius);
ctx.beginPath();
for(var i=0;i<5;i++) // 繪製標準五角星
{
ctx.lineTo(Math.cos((18+i*72)/180*Math.PI),-Math.sin((18+i*72)/180*Math.PI));
ctx.lineTo(Math.cos((54+i*72)/180*Math.PI)*0.5,-Math.sin((54+i*72)/180*Math.PI)*0.5);
}
ctx.closePath();
ctx.fillStyle="red";
ctx.fill();
ctx.restore();
radius+=dr;
if (radius>200 || radius<30)
dr=-dr;
}
setInterval("draw()",60);
</script>
</body>
</html>
在瀏覽器中打開包含這段HTML代碼的html文件,可以在瀏覽器視窗中看到如圖4所示的動畫。這個動畫效果的變化核心是語句“ctx.scale(radius,radius);”在起作用。
圖4 放大縮小的五角星
若將上面程式段中,radius固定取值120,再修改變化語句
radius+=dr;
if (radius>200 || radius<30)
dr=-dr;
為
rot=(rot+2)%360;
則五角星會進行旋轉,呈現出如圖5所示的動畫效果。
圖5 旋轉的五角星
4.遮罩動畫
利用Canvas API提供的裁切方法clip(),可以用來實現遮罩動畫。
例4 圖片圓形展開後收縮。
<!DOCTYPE html>
<head>
<title>圓形展開後收縮</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;">
</canvas>
<script type="text/javascript">
var r=10;
var dr=5;
var canvas=document.getElementById('myCanvas');
var ctx=canvas.getContext('2d');
var image = new Image();
image.src = 'aaa.jpg';
image.onload=function(){
ctx.drawImage(image,0,0);
}
setInterval("draw()",100);
function draw()
{
ctx.clearRect(0,0,400,400);
ctx.save();
ctx.beginPath();
ctx.arc(200,200,r,0,Math.PI*2,true);
ctx.closePath();
ctx.fillStyle="white";
ctx.fill();
ctx.clip();
ctx.drawImage(image,0,0);
ctx.restore();
r=r+dr;
if (r>280) dr=-5;
else if (r<=0) dr=5;
}
</script>
</body>
</html>
在瀏覽器中打開包含這段HTML代碼的html文件,可以在瀏覽器視窗中看到如圖6所示的動畫。
圖6 圓形展開後收縮
5.多個同類物體同時運動實現動畫
有時候設計動畫時,畫布中會有多個同類物體按各自的規律進行運動,這時將各物體抽象為對象數組比較方便處理。
例3中我們通過圖形變換的方法實現了五角星的放大和旋轉。下麵我們繪製60個五角星在畫布上進行移動的動畫效果。
例5 60個五角星隨機移動。
為了描述60個五角星,抽象一個星星對象Star。為該對象定義五角星中心位置坐標(x,y)、五角星外接圓半徑radius、水平方向移動速度speedX、垂直方向移動速度speedY和五角星旋轉角度deg等6個屬性。具體定義如下:
function Star()
{
this.x = randomNum(30,canvas.width-30);
this.y = randomNum(30,canvas.height-30);
this.radius=randomNum(8,12);
this.speedX = randomNum(-5,5);
this.speedY=randomNum(-5,5);
this.deg = randomNum(0,180);
}
為五角星對象定義兩個方法,一個是update方法,更新五角星的坐標位置(x,y)併進行邊界碰撞檢查;一個方法是draw方法,按屬性設置繪製出五角星。具體定義如下:
Star.prototype.update = function()
{
this.x += this.speedX;
this.y += this.speedY;
if (this.x-this.radius<=0)
{
this.speedX=-this.speedX;
this.x=this.radius;
}
if (this.x+this.radius>canvas.width)
{
this.speedX=-this.speedX;
this.x=canvas.width-this.radius;
}
if (this.y-this.radius<=0)
{
this.speedY=-this.speedY;
this.y=this.radius;
}
if (this.y+this.radius>canvas.height)
{
this.speedY=-this.speedY;
this.y=canvas.height-this.radius;
}
}
Star.prototype.draw = function()
{
ctx.beginPath();
for (var i = 0; i < 5; i ++)
{
ctx.lineTo( Math.cos( (18 + i*72 - this.deg)/180 * Math.PI) *this.radius + this.x,
-Math.sin( (18 + i*72 - this.deg)/180 * Math.PI) * this.radius + this.y)
ctx.lineTo( Math.cos( (54 + i*72 - this.deg)/180 * Math.PI) * this.radius/2+ this.x,
-Math.sin( (54 + i*72 - this.deg)/180 * Math.PI) * this.radius/2 + this.y)
}
ctx.closePath();
ctx.lineWidth = 3;
ctx.fillStyle = "#ff0000";
ctx.strokeStyle = "#ffff00";
ctx.lineJoin = "round";
ctx.fill();
ctx.stroke();
}
定義好五角星對象後,定義一個數組stars,保存60個五角星並設置動畫過程。編寫完整的HTML文件如下。
<!DOCTYPE html>
<html>
<head>
<title>滿天都是小星星</title>
</head>
<body>
<canvas id="myCanvas" width="500" height="400" style="border:3px double #996633;">
</canvas>
<script type="text/javascript">
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
function Star()
{
this.x = randomNum(30,canvas.width-30);
this.y = randomNum(30,canvas.height-30);
this.radius=randomNum(8,12);
this.speedX = randomNum(-5,5);
this.speedY=randomNum(-5,5);
this.deg = randomNum(0,180);
}
Star.prototype.update = function()
{
this.x += this.speedX;
this.y += this.speedY;
if (this.x-this.radius<=0)
{
this.speedX=-this.speedX;
this.x=this.radius;
}
if (this.x+this.radius>canvas.width)
{
this.speedX=-this.speedX;
this.x=canvas.width-this.radius;
}
if (this.y-this.radius<=0)
{
this.speedY=-this.speedY;
this.y=this.radius;
}
if (this.y+this.radius>canvas.height)
{
this.speedY=-this.speedY;
this.y=canvas.height-this.radius;
}
}
Star.prototype.draw = function()
{
ctx.beginPath();
for (var i = 0; i < 5; i ++)
{
ctx.lineTo( Math.cos( (18 + i*72 - this.deg)/180 * Math.PI) *this.radius + this.x,
-Math.sin( (18 + i*72 - this.deg)/180 * Math.PI) * this.radius + this.y)
ctx.lineTo( Math.cos( (54 + i*72 - this.deg)/180 * Math.PI) * this.radius/2+ this.x,
-Math.sin( (54 + i*72 - this.deg)/180 * Math.PI) * this.radius/2 + this.y)
}
ctx.closePath();
ctx.lineWidth = 1;
ctx.fillStyle = "white";
ctx.strokeStyle = "#ffff00";
ctx.lineJoin = "round";
ctx.fill();
ctx.stroke();
}
function randomNum(min,max)
{
return Math.floor(Math.random()*(max-min+1)+min);
}
var stars = [];
for (var i = 0; i < 60; i++)
{
stars.push(new Star());
if (stars[i].speedX==0 && stars[i].speedY==0)
stars[i].speedX=stars[i].speedY=1;
}
function move()
{
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle="blue";
ctx.fillRect(0,0,canvas.width,canvas.height);
for (var i = 0; i <60; i++)
{
stars[i].draw();
stars[i].update();
}
}
setInterval("move()",10);
</script>
</body>
</html>
在瀏覽器中打開包含這段HTML代碼的html文件,可以在瀏覽器視窗中看到如圖7所示的動畫。
圖7 星星在運動
例6 下雪了。
簡單模擬下雪場景,編寫如下的HTML文件。在屏幕中最多有100片雪花,每片雪花繪製一個小圓表示,從畫布頂端開始往下落。由於動畫過程一直在迴圈,因此每當一片雪花落出畫布之外後,隨機為其賦予水平坐標、置垂直坐標置為0、並隨機設置其圓半徑和下落速度,表示這是一片新雪花。這樣,用一個具有100個元素的數組即可保存屏幕中下落雪花的信息。
<!DOCTYPE html>
<html>
<head>
<title>下雪了</title>
</head>
<body>
<canvas id="myCanvas" width="300" height="300" style="border:3px double #996633;">
</canvas>
<script>
var canvas=document.getElementById('myCanvas');
var ctx=canvas.getContext('2d');
var particles = [];
function loop()
{
createParticles();
downParticles();
drawParticles();
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);
function createParticles()
{
if(particles.length <100)
{
particles.push({
x: Math.random()*canvas.width,
y: 0,
speed: 2+Math.random()*3,
radius: 3+Math.random()*4,
});
}
}
function downParticles()
{
for(var i in particles)
{
var part = particles[i];
part.y += part.speed;
if(part.y > canvas.height)
{
part.x=Math.random()*canvas.width;
part.y=0;
part.speed=2+Math.random()*3;
part.radius=3+Math.random()*4;
}
}
}
function drawParticles()
{
ctx.fillStyle = "black";
ctx.fillRect(0,0,canvas.width,canvas.height);
for(var i in particles)
{
var part = particles[i];
ctx.beginPath();
ctx.arc(part.x,part.y, part.radius, 0, Math.PI*2);
ctx.closePath();
ctx.fillStyle = "white";
ctx.fill();
}
}
</script>
</body>
</html>
在瀏覽器中打開包含這段HTML代碼的html文件,可以在瀏覽器視窗中看到如圖8所示的動畫。
圖8 下雪了