現在網上有很多javascript特效,這些特效最大的特點就是“會動”,也就是動畫效果。實際上近年來使用javascript在網頁中實現動畫已經成為一種趨勢,而前幾年在網頁動畫領域相當風騷的Flash今年來也稍顯低調。畢竟作為富用戶端程式,Flash對於目前讓人糾結的頻寬來說還是稍顯笨重了點兒,而且Flash必須要有外掛程式支援。但javascript這種用戶端指令碼語言卻可以只佔用相當少的空間而且不需要繁瑣的外掛程式支援和下載就能很好的解決一些簡單的動畫問題,例如:運動、變形等等。
現在有很多優秀的類庫已經做的很好了,類似於JQuery、ExtJs、prototype等,還有一些小型的animation、easing類庫都實現的很好,不過細究之下,發現網上相關的詳解資料很少。包括Tween演算法,也是只有例子沒有詳細的解釋,當然,不乏高手,只不過可能高手很少願意在這上面花時間做解釋吧。
下面我將盡本人所能來詳細的解釋相關的運動演算法和在javascript中的應用執行個體,並儘可能多的提供相關資料供大家學習。
一、運動演算法涉及到一些高中物理和數學知識
我是文科出身的,高中數理化相當爛。我最輝煌的一次數學成績是9分,物理14分,有木有童鞋比我的還低?木有吧?恩,沒人回答我就先當沒有了...那麼,我相信下面的一些知識對你來說應該相當easy。
運動:根據我們這裡的需求,我們簡單的把運動定義為隨著時間的改變,物體的位置不斷改變的現象。這裡有三個要素:時間、起始位置、終點位置。而且位置和時間之間存在一對一的對應關係。
那麼在javascript中,時間的問題我們可以用setInterval或者setTimeOut解決,位置的問題我們可以通過DOM對象的style屬性控制。
設p(position)為位置,t(time)為時間,那麼有
1: p = ƒ(t)
轉換為javascript函數就是:
1: p = function(t){
2: var v = 2;//速度
3: return t*v;}
我們知道速度 v(velocity) = 距離/時間,那麼我們設距離為c(change positioin),經過時間為d(druation),那麼
p = t * ( c / d )
我們可以通過上面這個公式得到物體在(c/d)的速度下移動時間t所在的位置p,但請注意,這裡其實有個前提:物體的初始位置為0。
那麼如果我們設物體的當前位置為b(begining positoin),那麼
p = t * ( c / d ) + b
將這個公式代入javascript中,得(終於有高中算方程題的感覺了)
1: p = funciton( t, b, c, d ){
2: return t * ( c / d ) + b;
3: }
格式化,得
1: Math.prototype.linearTween = function( t, b, c, d){
2: return t*c/d + b;
3: }
這樣,我們就得到了最簡單的直線運動函數。
為了更加形象的描述問題,我們引入座標系,那麼直線運動在這裡就可以表示為(圖1),這就是我們高中數學中最簡單的線性運算式:y=x;此時斜率為1。
圖1
二、一個簡單的例子
那麼下面我們將這個公式引入到javascript中,做一個簡單的例子來看一下實際的效果。
1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2: <html xmlns="http://www.w3.org/1999/xhtml">
3: <head>
4: <title>TweenTest</title>
5: <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6: <meta name="generator" content="NTCSoft" />
7: <meta name="author" content="MrZhou" />
8: <meta name="keywords" content="" />
9: <meta name="description" content="" />
10: <style type="text/css">
11: #moveLinear{
12: width:50px;
13: height:50px;
14: background:#ccc;
15: border: 1px solid;
16: }
17: </style>
18:
19: </head>
20:
21: <body>
22: <div id="moveLinear"></div>
23: </body>
24: <script type="text/javascript">
25:
26: //將$設定為擷取元素對象的函數,留待以後使用。
27: $ = function( id ){ return typeof id == "string"?document.getElementById(id):id }
28:
29: /* des:tween演算法。
30: t: 動畫已經執行的時間(實際上時執行多少次/幀數)
31: b: 起始位置
32: c: 終止位置
33: d: 從起始位置到終止位置的經過時間(實際上時執行多少次/幀數)*/
34: tween = {
35: linear : function( t, b, c, d){
36: return t*c/d + b;
37: }
38: }
39: /* des: 控制元素移動的動畫對象
40: moveType : 元素移動的方式,預設為linear 。
41: 參數:
42: mvTp = string 移動方式
43: startMove : 移動元素的函數
44: 參數:
45: mvObj = string 被移動的元素的id
46: mvTp = string 移動方式*/
47: move={
48: //元素移動的方式:預設為linear
49: moveType : function(mvTp){return mvTp && typeof(mvTp)== ”string” && tween[mvTp] ? mvTp: "linear" },
50:
51: //移動元素的函數
52: //mvObj:string 被移動的元素的id
53: //mvTp:string 移動類型
54: startMove : function( mvObj,mvTp,t,b,c,d ){
55:
56: //判斷傳入參數是否正確,如果t存在那麼還是t,否則為t設定預設值,其他的以此類推
57: t ? t : t=0; b ? b : b = 0; c ? c : c =300; d ? d : d = 50;
58: //判斷被移動的元素是否存在相對或者絕對的定位屬性,1 的作用僅僅是為了完成文法格式(若沒有該屬性,則無法移動元素)
59: $(mvObj).style.position =="relative" || $(mvObj).style.position =="absolute"
60: ? 1 : $(mvObj).style.position = "relative";
61:
62: //每隔30毫秒重複執行改變元素位置的函數
63: mvTimer = setInterval(function(){
64: //判斷動畫已經執行的時間(次數/幀數)是否小於總時間,是的話繼續執行改變位置的函數,否則的話,清理該interval。
65: //?和:之間的函數是個匿名函數,在匿名函數的後面加上()直接調用該函數,簡便寫法。
66: //tween[move.moveType(mvTp)]() 先通過對象數組的屬性obj["x"]方式訪問tween的某個屬性對象,然後加上()執行該函數對象。
67: t <= d
68: ? function(){ $(mvObj).style.left = parseInt(tween[move.moveType(mvTp)](t,b,c,d))+"px"; t++;}()
69: : clearInterval(mvTimer);
70: } ,30 )
71: }
72: }
73:
74: move.startMove("moveLinear");
75: </script>
76: </html>
通過上面你的動畫,我們發現灰色方框可以很順利的從左邊移動到右邊,這好像已經達到了我們的要求,但如果我們再上面這個例子簡單的將一個方框從左邊的0px以6px/30ms的速度移動到右邊300px的位置(這裡為什麼是以6px/30ms的速度等下解釋),現在我們來看一下動畫消耗時間,按照我們的理論,每30ms移動6px,那麼300px的動畫時間應該是30ms * 50 = 1500ms,但如果童鞋們再細心的測試一下,就會發現,動畫總時間並不像我們想象的那樣準確。
為此,我們在程式中間加兩個時間戳記,來計算動畫總共的時常:
1: ……
2: $(mvObj).style.position =="relative" || $(mvObj).style.position =="absolute"
3: ? 1 : $(mvObj).style.position = "relative";
4: //開始時間戳
5: strT = new Date();
6: //每隔30毫秒重複執行改變元素位置的函數
7: mvTimer = setInterval(function(){
8: ……
1: ……
2: ? function(){ $(mvObj).style.left = parseInt(tween[move.moveType(mvTp)](t,b,c,d))+"px"; t++;}()
3: //結束時間戳記,並彈齣動畫總時間長度
4: : function(){ clearInterval(mvTimer); alert(new Date() - strT); }()
5: } ,30 )
6: ……
得出如下結果:
而且,如果你多測試幾次發現每次的結果可能都不盡相同,這對我們精確控制動畫執行時間絕對是個噩耗.深究了一下,發現時間誤差的原因還真是比較讓人糾結。
那麼在我們進入下一階段之前,就先來看一下這個問題,省的到了後面問題複雜度增加的時候再解決就相對比較麻煩了。