基於Canvas的動畫基本原理與數理分析

什麼是動畫?

就像思考哲學問題沒法迴避思惟和存在的關係同樣,製做動畫一樣沒法逃避的問題是動畫的原理是什麼?這裏提一句題外話,任何原理的東西一般難以讓你短時間拾掇成果,但在隱約的將來會起到難以置信的效果,不信就看接下來小羊的一些學習成果分享。html

馴龍高手

動畫本質上是圖像按照事先設定好的順序在必定的時間內的圖像序列變化運動。這種圖像序列的變化運動給咱們最爲直觀的感覺就是圖像彷彿真實的在運動通常,由此產生動畫效果。git

而後,事實並不是如此,真相每每難以用肉眼觀察獲得,除非你是上帝~~~github

動畫的特性在於:web

  • 每一張圖像的內容是事先設定好的,內容是不變的,變化的是圖像序列按照規定的順序在變更;canvas

  • 構成動畫特效須要在單位時間內渲染必定量的圖像,每張圖像稱之爲幀(Frame),一般電影只須要24FPS就足夠流暢,而遊戲則須要60FPS,咱們設計動畫時一般選用60FPS;瀏覽器

總之,你所看到的動畫無非是你的眼睛在欺騙你的大腦,本質上每一張圖像仍是那張圖像,只不過它們在單位時間內按照必定順序在快速移動罷了~~~app

Canvas API的簡介

這一部分爲了兼顧以前未接觸canvas元素的看官以及重溫canvas的目的,幫助小羊和各位快速過一遍canvas的API。函數

//demo.html
<canvas id='canvas' width='500' height='500'></canvas> 
[注]
設置canvas的寬高要在元素或在其API,canvas.width || canvas.height,在CSS上設置爲獲得意想不到的結果,不信試試看哈···

//demo.js
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

//路徑或圖形
context.fillRect();//填充矩形
context.strokeRect();//勾勒矩形輪廓
context.arc(x,y,r,anglestart,angleend,clockwise);//畫弧形

context.beginPath();//開始路徑
context.moveTo();//定義路徑起始點
context.lineTo();//路徑的去向
context.closePath();//畫完後,關閉路徑
context.fill() || context.stroke();//最後畫出由路徑構成的圖形
[注]
本質上,全部的多邊形均可以由路徑畫出;

context.save();//保存save以上context對象設置的屬性值
context.restore();//恢復到先前保存在context對象上的全部屬性值

這裏在介紹一下實現動畫效果的很是重要的API:學習

window.requestAnimationFrame(callback)

先前我已經說過,動畫是在單位時間內按照必定順序的圖像序列的變化造成的;這個API的功能就是,你能夠在回調函數裏面寫一個腳本改變圖形的寬高,而後這一API就會根據瀏覽器的刷新頻率而在必定時間內調用callback;而後,根據遞歸的思想,實現callback的反覆調用,最終實現動畫效果。測試

不明白,上代碼

(function drawFrame(){
    window.requestAnimationFrame(drawFrame);
    
    //some code for animation effect here
})();

上面的代碼意思是當即執行drawFrame這個函數,發現 window.requestAnimationFrame(drawFrame),okay根據瀏覽器的刷新頻率,在必定時間以後執行;接下來執行你所編寫的改變圖像內容(圖像的位置、寬高、顏色等等)的腳本,執行回調;循環反覆,造成動畫效果

由此也可知道:

window.requestAnimationFrame這個API你能夠理解爲window.setTimeout(callback,time)

事實上,當部分瀏覽器不兼容這個API時,咱們也能夠寫成如下形式:

if(!window.requestAnimationFrame){
    window.requestAnimationFrame = (
      window.webkitRequestAnimationFrame || 
      window.mozRequestAnimationFrame ||
      window.msRquestAniamtionFrame ||
      window.oRequestAnimationFrame || 
      function (callback){
          return setTimeout(callback,Math.floor(1000/60))
    }
  )
}

Okay,有了這麼幾個基本的canvasAPI就足以應對接下來的知識點了,若有不懂或深刻了解,詳見Canvas教程-MDN

【注】如下全部代碼託管到【github】

動畫的數理分析

有了前面的基礎知識,如今咱們就會想:若是咱們可以在每16ms(1秒60幀,1000/60)內渲染1張圖像,而且每一張圖像的內容發生微調,那麼在1秒鐘整個畫面就會產生動畫效果了。

內容的微調能夠是圖形的移動的距離、轉動的方向以及縮放的比例等等,而「捕獲」這些數據的方法就用使用到咱們之前忽視的解析幾何的知識了。

移動的距離

1. 線性運動

線性運動就是物體朝特定方向的運動,運動過程當中速度不發生改變;

linear-motion

<canvas id="canvas"  width="500" height="500" style="background:#000"></canvas>
    <script src='../js/utils.js'></script>
<script src='../js/ball.js'></script>
    <script>

    //這個腳本中,勻速運動的原理是經過連續改變原點在x軸上的座標,從而實現勻速運動;
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');

        var ball = new Ball();

        var xspeed = 1;//定義每渲染1幀,圖形在x軸移動的距離(移動原點)    

        (function drawFrame(){
            window.requestAnimationFrame(drawFrame,canvas);
            context.clearRect(0,0,canvas.width,canvas.height);

            ball.x += xspeed;
            ball.y = canvas.height/2;

            if(ball.x>canvas.width+ball.radius){
                ball.x = -ball.radius;
            } 

            ball.draw(context);
        })();

    </script>

這段代碼涉及部分封裝的函數,這裏就不講,具體源碼能夠參考【github】

這裏主要講解一下思路,若是咱們須要圓在x軸上移動,那麼一個思路是改變圓的圓心,使圓心在x軸上不斷變化,最終造成動畫的效果;上面的ball.x = xpeed就是每執行一次RAF(window.requestAnimationFrame),圓心就向右移動1像素;同理能夠實如今圓在y軸上的移動,甚至是x和y軸的同時移動,這樣涉及向量的合成知識了。

圖片來源:周餘飛

如今大夥是否是深入理解高中和大學時學的看似無用的解析幾何的妙用啦,天然界物體的運動規律不正是遵循着這些迷人的數學等式嗎?

好吧,扯遠了,言歸正傳~~~小結一下物體的勻速運動:

  1. 物體的勻速運動無非是改變其在座標軸的值,可是每次的改變量是不變的,也就是單位時間內的移動距離是不變的,這樣才符合勻速;

  2. 經過向量的合成原理,咱們能夠在canvas畫布上實現任意方向的勻速運動

【linear-motion】

2. 變速運動

如同你的知識積澱同樣,當你學的越多,運用的越靈活,你再去get一門新的技能的時候,學習的速度就不在是勻速運動,而是變速運動了。

變速運動,本質上是物體運動過程當中速度在變化,也就是之前學過的加速度的概念,也就是說要想實現物體變速運動,只須要改變座標軸的值,每次的改變是變化的

nonlinear-motion

<canvas id="canvas"  width="500" height="500" style="background:#000"></canvas>
    <script src='../js/utils.js'></script>
<script src='../js/ball.js'></script>
    <script>

   
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');

        var ball = new Ball();

        var xspeed = 1;//定義每渲染1幀,圖形在x軸移動的距離(移動原點)    
        var ax = 0.5;//設置x軸上的每渲染1幀xspeed增長0.05;

        (function drawFrame(){
            window.requestAnimationFrame(drawFrame,canvas);
            context.clearRect(0,0,canvas.width,canvas.height);

            ball.x += xspeed;
            xspeed += ax;
            ball.y = canvas.height/2;
            //ball.y += 1;


            if(ball.x>canvas.width+ball.radius){
                ball.x = -ball.radius;
            } 

            ball.draw(context);
        })();


    </script>

【nonlinear-motion】

看完上面的代碼有沒有感到很神奇,一樣一段代碼,只須要添加var vx = 0.5xspeed+=vx就可使物體實現加速運動;看完demo後,你有沒有發現,當速度達到必定程度的時候,物體給人的感受好像是靜止同樣;

【注】
這裏給大夥講一個上面非線性運動平常例子,也就是籃球的自由落體運動,先給大夥看看演示的效果:

gravity-acceleration

中學物理都有學過,物體在自由落體過程當中受萬有引力和空氣摩擦力的協力——重力的影響而向下運動,球體在落地地面給了物體一個做用力致使物理受向上的力和萬有引力的影響而向上運動,循環反覆,由此出現上面的運動效果;

數理分析上,物體先是作向下的加速運動,落到地面後(有一個落到地面的速度)再作向上的減速運動知道速度爲0時,再作向下的加速運動,循環反覆,知道小球落到地面;

<script>

    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');

    var ball = new Ball(20,'white');


    //設置小球初始降落的位置
    ball.x = canvas.width/2;
    ball.y = canvas.height/5;

    var vy = 0;
    var gravity = 0.05;//定義重力加速度;
    var bounce = -0.8;//定義反彈係數;

    //碰撞測試
    function checkGround(ball){
        if(ball.y+ball.radius>canvas.height){

            //小球碰到地面時,讓球的位置暫時設置爲在地面上
            ball.y = canvas.height - ball.radius;

            //此時設置小球落到地面時的速度爲反向,大小爲原來的0.8;
            vy *= bounce;
        }
    }

    (function drawFrame(){
        window.requestAnimationFrame(drawFrame,canvas);
        context.clearRect(0,0,canvas.width,canvas.height);

        //小球首先作向下的加速運動
        vy += gravity;
        ball.y += vy;

        //碰撞測試,當小球下落到地面時,vy *= bounce;
        //此時小球在地面時的初始速度爲vy *= bounce(vy此時是負值),接着繼續向上運動,每渲染1幀,vy+=gravity,注意此時小球作向上的減速運動,直到速度爲0時;
        //接着小球繼續作向下加速運動,循環往復,直到小球中止;
        checkGround(ball)

        ball.draw(context);
    })();

</script>

【gravity-acceleration】

各位能夠嘗試去修改gravity和bounce的參數,你會有意想不到的結果發現;

3. 波形運動

波形運動,顧名思義像波浪般的運動,運動軌跡如同下面的三角函數通常;
此時,咱們不由會想:要實現波形運動的軌跡,是否是須要用到三角函數呢?
Bingo,答對了~~~但是知識已經還給我敬愛的老師嘞;

圖片來源:周餘飛

別緊張,其實實現這個軌跡so easy,聽我娓娓道來······

先分析一下思路:
實現圓按照波形運動的動畫原理是什麼?
每16ms瀏覽器渲染1幀,每1幀圓的圓心位置發生改變,改變的路徑就是這個正弦函數;

waving-motion

還不懂?答案就是跟前面的代碼如出一轍,只不過x軸的變化值由

//勻速運動
ball.x = xspeed//xspeed = 1一直都是1;

//變速運動
var ax = 0.05;
ball.x += xspeed //xspeed = 1初始值爲1;
xspeed += ax//每16ms,xspeed增長0.05;
【注】
各位童鞋本身想一下曲線運動如何實現?提示一下,結合勻速運動和變速運動一塊兒思考;
[【curve-motion】](http://terenyeung.applinzi.com/newapp/canvas/html/curve-motion.html)

//波形運動
var angle = 0;//定義每次變化的角度
var swing = 100;//定義振幅;


ball.x +=2;
ball.y  = canvas.height/2 + Math.sin(angle)*swing;
angle += 0.1;

完整代碼以下:

<script>

    
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');

        var ball = new Ball();

        var angle = 0;
        var swing = 100;
    

        (function drawFrame(){
            window.requestAnimationFrame(drawFrame,canvas);
            context.clearRect(0,0,canvas.width,canvas.height);

            ball.x += 2;
            ball.y = canvas.height/2+Math.sin(angle)*swing;
            //ball.y += 1;
            angle += 0.1;

            if(ball.x>canvas.width+ball.radius){
                ball.x = -ball.radius;
            } 

            ball.draw(context);
        })();


    </script>

【waving-motion】

細心的童鞋可能已經發現這麼一個規律:物體的運動軌跡無非就是經過改變物體在canvas座標軸上的值+RAF這個API而產生運動的;

勻速運動設置ball.x += 1,每頻次圖形的x軸右移1px;

變速運動設置ball.x += vx, vx += ax,每頻次圖形x軸右移vx後,vx加ax,下一次圖形將移動vx+ax從而實現變速;

波形運動則設置ball.y = centerY + Mathsin(angle)*swing,因爲正弦函數的值區間爲[-1,1],因此圖形會永遠在[centerY-swing,centerY+swing]上下移動;

這一種思想將會對後面的圖形運動的思考一樣奏效;

4. 圓形運動
如今咱們再想一下,如何讓圓圍繞一個點作圓周運動?
咱們學到的解析幾何有什麼是能夠表示圓的?相信各位童鞋已經學會開始搶答了,對啦就是

x*x+y*y = r*r//這是一原點爲圓心,半徑爲r的圓;

或許有童鞋會問候我尼瑪,你剛纔不是告訴我實現物體運動,只要按照RAF改變物體座標軸的值就好了嗎,你給我上面這麼一個等式,那我怎麼樣去給ball.x和ball.y賦值;

人類一思考,上帝就發笑,這是小羊寫這篇文章時新鮮看到的一句話,我一開始的理解爲嘲諷人類的自做聰明,後來想一下我更加願意理解爲上帝是在對人類不斷追求真理這一行爲的勉勵把;

circular-motion

若是有看官想到這一層面,我會以爲你很牛X,由於我是過後複習纔想到這一點的。不賣關子,你們應該據說過極座標把(再一次驗證原理的有效性)

//圓的極座標表達式爲
x = rcosθ
y = rsinθ

也就是說給我一個圓的半徑和每次旋轉的角度,我就能夠用x和y的方式描繪圓的路徑

二話不說上代碼:

<script>

    
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');

        var ball = new Ball();

        var angle = 0.1;
        var scope = 100;
    
        (function drawFrame(){
            window.requestAnimationFrame(drawFrame,canvas);
            context.clearRect(0,0,canvas.width,canvas.height);

            ball.x = canvas.width/2+Math.cos(angle)*scope;
            ball.y = canvas.height/2+Math.sin(angle)*scope;
            //ball.y += 1;
            angle += 0.1;

            // if(ball.x>canvas.width+ball.radius){
            //     ball.x = -ball.radius;
            // } 

            ball.draw(context);
        })();


    </script>

【circular-motion】

有了圓形運動,再講一下橢圓運動,思考過程和上面基本同樣,數學表達式爲:

(x/a)*(x/a)+(y/b)*(y/b)=1
//極座標
x = a*cosθ
y = b*sinθ

有了這兩個座標,圖形的橢圓路徑還不出來嗎,相信你已經躍躍欲試了,我這裏就直接給demo啦。

ellipse-motion

【ellipse-motion】

其實,圓形運動本質上就是特殊的橢圓運動,各位能夠看一下兩者的聯繫與區別:

//圓形運動
var angle = 0,scope = 100;
x = canvas.width/2 + scope*Math.cos(angle)
y = canvas.height/2 + scope*Math.sin(angle)
angle += 0.1;

//橢圓運動
var angle = 0,scopeX = 150 , scopeY = 80;
x = canvas.width/2 + scopeX*Math.cos(angle)
y = canvas.height/2 + scopeY*Math.sin(angle)
angle += 0.1;

轉動的方向

動畫特效當中其中有一個很重要的點就是物體的轉動方向問題,以天然界的實例來看,你會看到地球自轉及其圍繞太陽公轉;

self-rotation

resolution

這裏先給上一段實現封裝好的Arrow類,用於後面的講解所用;

//arrow.js
function Arrow(){
    this.x = 0;
    this.y = 0;
    this.rotation = 0;
    this.color = '#ff0';
};

Arrow.prototype.draw = function(context){
    context.save();
    context.translate(this.x,this.y);
    context.rotate(this.rotation);
    context.lineWidth = 5;
    context.beginPath();
    context.moveTo(-50,-25);
    context.lineTo(0,-25);
    context.lineTo(0,-50);
    context.lineTo(50,0);
    context.lineTo(0,50);
    context.lineTo(0,25);
    context.lineTo(-50,25);
    context.closePath();
    context.stroke();
    context.fill();
    context.restore();
};

小羊在轉動的方向這一部分要使用一個canvas的新API——context.rotate(angle)來控制物體的轉動;

到如今只要你掌握前面所講的動畫原理的話,那麼就不難推理出自轉和公轉的動畫來;

自轉:每16ms變化一次angle,那麼angle做爲參數每傳遞1次,物體就會轉動1次,最終造成自轉

window.onload = function(){
    var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');

        var arrow = new Arrow();

        var angle = 0;
        arrow.x = canvas.width/2;
        arrow.y = canvas.height/2;

        (function drawFrame(){
            window.requestAnimationFrame(drawFrame,canvas);
            context.clearRect(0,0,canvas.width,canvas.height);
            arrow.rotation = angle;
            angle +=0.1;

            arrow.draw(context);
        })();

  }

【self-rotation】

公轉:使用圓周運動的方法實現公轉;
【resolution】

上面的angle的賦值是機械式,若是咱們想要鼠標轉到哪裏,箭頭就指到哪裏,會不會更加具備交互性;

物體轉動的角度和鼠標的指向有關,那麼如何創建兩者之間的聯繫呢?

圖片來源:周餘飛

上圖給出了答案:先是獲取到鼠標在canvas上的座標,而後獲取到物體中心的座標,根據二點間的距離公式,能夠測算出鼠標距離中心點在x軸和y軸的份量dx和dy,而後經過一個很牛掰的三角函數,

object.rotation = Math.atan2(dy,dx);

這個三角函數做用是給它兩個x和y軸的距離份量,就能夠測算出鼠標與x軸的夾角來;
有同窗會問:問什麼能夠這樣?這個暫時沒法回答,這個問題深究下去就不屬於本筆記範圍以內了,知道有這麼一個方法就okay啦;

測算出角度,就能夠給context.rotation(angle)傳參啦,此時箭頭將會跟着鼠標轉動;

window.onload = function(){
    var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');
        var mouse = utils.captureMouse(canvas);
        var arrow = new Arrow();

        var angle = 0;
        arrow.x = canvas.width/2;
        arrow.y = canvas.height/2;

        (function drawFrame(){
            window.requestAnimationFrame(drawFrame,canvas);
            context.clearRect(0,0,canvas.width,canvas.height);
            
            var dx = mouse.x - arrow.x,
                dy = mouse.y - arrow.y;
            angle = Math.atan2(dy,dx);
            arrow.rotation = angle;
            arrow.draw(context);
        })();

  }

【rotation-to-cursor】

okay,如今有了轉動,再加入先前的物體運動就可讓"讓子彈飛"啦~~~
【cursor-follow】

這個demo是一個小飛機,你按下啥鍵它就會飛向哪,真正實現和用戶交互;
【spaceShip】

【注】Math.atan2(dy,dx)函數很重要!!!,這意味着這要你可以測算出鼠標與指定點之間的x軸和y軸的份量,那麼你就能夠獲取到鼠標與指定點的連線與x軸所造成的的夾角,由此就能夠去改變物體的運動或是轉向;

縮放的比例

canvas提供縮放功能的API可讓咱們對物體進行縮放大小,若是結合咱們以前學的一些解析幾何的知識,那麼就能夠創做出變幻無窮的縮放特效出來;

plusing-motion

<script>
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');
        var ball = new Ball();
            ball.x = canvas.width/2;
            ball.y = canvas.height/2;

        var angle = 0,
            centerScale = 1;
            swing = 0.5;
    
        (function drawFrame(){
            window.requestAnimationFrame(drawFrame,canvas);
            context.clearRect(0,0,canvas.width,canvas.height);

            angle += 0.05

            //plusing-effect
            ball.scaleX = ball.scaleY = centerScale + Math.sin(angle)*swing;
        
            //bigger and bigger effect
            //ball.scaleX = ball.scaleY = centerScale + angle
            ball.draw(context);
        })();
    </script>

【plusing-motion】

總結

本篇文章題目是《基於Canvas的動畫基本原理與數理分析》,所以只介紹了一些在使用canvas元素繪製動畫時運用到的一些經常使用的解析幾何原理和相關的物理知識,例如勻速運動、變速運動、圓周運動、波形運動、脈衝運動,這些運動過程當中可涉及到的概念又包括向量的分解(力的分解)、重力、摩擦力、加速度、三角函數等等······

物體的屬性——在canvas上的位置、方向和比例的變化萬千無非是使用上述的數學等式經過RAF從而造成動畫效果的;

固然,僅僅掌握這些並不足以讓你設計出至關出彩的動畫特效,可是就像開頭所說的這部分的知識點至關因而動畫的基本原理,它相對於直接使用CSS3作動畫來的艱深複雜些,短時間可能會花咱們一些時間去理解,可是日後保不許會排上用場,反正我在學習的過程當中就充滿力量感——由於尼瑪高中學的物理和大學學的高數終於排上用場啦!!!

canvas動畫的知識點還包括一些具體的應用和晉升到3D的層次,這一部分的內容就留給童鞋們自行解決,冥冥之中我有種預感,你只要把上面的掌握住了,高級的就不成問題了;

寫完這篇文章,感受本身要吐血三升!!!具體是多少字數,本身也沒數,可能會比較長,若是有看官耐住性子看到這裏,我可能會很佩服你喲······

最後,要感謝周餘飛童鞋的技術博客的指導,本篇文章是學習周餘飛同窗【每週一點canvas動畫系列】的學習心得和思考,沒有做者的辛勤付出,也就沒有個人知識汲取後的再度分享,爲此我在下面給出做者於【github】的地址,有興趣的同窗,可進一步學習和探討,THX。

參考資料
【周餘飛——每週一點canvas動畫系列】

相關文章
相關標籤/搜索