基于Canvas的動(dòng)畫(huà)基本原理與數(shù)理分析

什么是動(dòng)畫(huà)?

就像思考哲學(xué)問(wèn)題無(wú)法回避思維和存在的關(guān)系一樣,制作動(dòng)畫(huà)同樣無(wú)法逃避的問(wèn)題是動(dòng)畫(huà)的原理是什么?這里提一句題外話,任何原理的東西通常難以讓你短期拾掇成果,但在隱約的未來(lái)會(huì)起到難以置信的效果,不信就看接下來(lái)小羊的一些學(xué)習(xí)成果分享。

馴龍高手

動(dòng)畫(huà)本質(zhì)上是圖像按照事先設(shè)定好的順序在一定的時(shí)間內(nèi)的圖像序列變化運(yùn)動(dòng)
這種圖像序列的變化運(yùn)動(dòng)給我們最為直觀的感受就是圖像仿佛真實(shí)的在運(yùn)動(dòng)一般,由此產(chǎn)生動(dòng)畫(huà)效果。
然后,事實(shí)并非如此,真相往往難以用肉眼觀察得到,除非你是上帝~~~
動(dòng)畫(huà)的特性在于:

  • 每一張圖像的內(nèi)容是事先設(shè)定好的,內(nèi)容是不變的,變化的是圖像序列按照規(guī)定的順序在變動(dòng);
  • 構(gòu)成動(dòng)畫(huà)特效需要在單位時(shí)間內(nèi)渲染一定量的圖像,每張圖像稱(chēng)之為幀(Frame),通常電影只需要24FPS就足夠流暢,而游戲則需要60FPS,我們?cè)O(shè)計(jì)動(dòng)畫(huà)時(shí)通常選用60FPS;

總之,你所看到的動(dòng)畫(huà)無(wú)非是你的眼睛在欺騙你的大腦,本質(zhì)上每一張圖像還是那張圖像,只不過(guò)它們?cè)趩挝粫r(shí)間內(nèi)按照一定順序在快速移動(dòng)罷了~~~

Canvas API的簡(jiǎn)介

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

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

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

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

context.beginPath();//開(kāi)始路徑
context.moveTo();//定義路徑起始點(diǎn)
context.lineTo();//路徑的去向
context.closePath();//畫(huà)完后,關(guān)閉路徑
context.fill() || context.stroke();//最后畫(huà)出由路徑構(gòu)成的圖形
[注]
本質(zhì)上,所有的多邊形都可以由路徑畫(huà)出;

context.save();//保存save以上context對(duì)象設(shè)置的屬性值
context.restore();//恢復(fù)到先前保存在context對(duì)象上的所有屬性值

這里在介紹一下實(shí)現(xiàn)動(dòng)畫(huà)效果的非常重要的API:

window.requestAnimationFrame(callback)
先前我已經(jīng)說(shuō)過(guò),動(dòng)畫(huà)是在單位時(shí)間內(nèi)按照一定順序的圖像序列的變化形成的;
這個(gè)API的功能就是,你可以在回調(diào)函數(shù)里面寫(xiě)一個(gè)腳本改變圖形的寬高,然后這一API就會(huì)根據(jù)瀏覽器的刷新頻率而在一定時(shí)間內(nèi)調(diào)用callback;
然后,根據(jù)遞歸的思想,實(shí)現(xiàn)callback的反復(fù)調(diào)用,最終實(shí)現(xiàn)動(dòng)畫(huà)效果;
不明白,上代碼
(function drawFrame(){
    window.requestAnimationFrame(drawFrame);
    
    //some code for animation effect here
})();

上面的代碼意思是立即執(zhí)行drawFrame這個(gè)函數(shù),發(fā)現(xiàn)  window.requestAnimationFrame(drawFrame),okay根據(jù)瀏覽器的刷新頻率,在一定時(shí)間之后執(zhí)行;
接下來(lái)執(zhí)行你所編寫(xiě)的改變圖像內(nèi)容(圖像的位置、寬高、顏色等等)的腳本,執(zhí)行回調(diào);
循環(huán)反復(fù),形成動(dòng)畫(huà)效果

由此也可知道:

window.requestAnimationFrame這個(gè)API你可以理解為window.setTimeout(callback,time)

事實(shí)上,當(dāng)部分瀏覽器不兼容這個(gè)API時(shí),我們也可以寫(xiě)成以下形式:


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

Okay,有了這么幾個(gè)基本的canvasAPI就足以應(yīng)對(duì)接下來(lái)的知識(shí)點(diǎn)了,如有不懂或深入了解,詳見(jiàn)Canvas教程-MDN

【注】
以下所有代碼托管到【github】

動(dòng)畫(huà)的數(shù)理分析

有了前面的基礎(chǔ)知識(shí),現(xiàn)在我們就會(huì)想:如果我們能夠在每16ms(1秒60幀,1000/60)內(nèi)渲染1張圖像,并且每一張圖像的內(nèi)容發(fā)生微調(diào),那么在1秒鐘整個(gè)畫(huà)面就會(huì)產(chǎn)生動(dòng)畫(huà)效果了。

內(nèi)容的微調(diào)可以是圖形的移動(dòng)的距離、轉(zhuǎn)動(dòng)的方向以及縮放的比例等等,而“捕獲”這些數(shù)據(jù)的方法就用使用到我們以前忽視的解析幾何的知識(shí)了。

移動(dòng)的距離
  • 線性運(yùn)動(dòng)
    線性運(yùn)動(dòng)就是物體朝特定方向的運(yùn)動(dòng),運(yùn)動(dòng)過(guò)程中速度不發(fā)生改變;
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>

    //這個(gè)腳本中,勻速運(yùn)動(dòng)的原理是通過(guò)連續(xù)改變?cè)c(diǎn)在x軸上的坐標(biāo),從而實(shí)現(xiàn)勻速運(yùn)動(dòng);
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');

        var ball = new Ball();

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

        (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>

這段代碼涉及部分封裝的函數(shù),這里就不講,具體源碼可以參考【github】
這里主要講解一下思路,如果我們需要圓在x軸上移動(dòng),那么一個(gè)思路是改變圓的圓心,使圓心在x軸上不斷變化,最終形成動(dòng)畫(huà)的效果;
上面的ball.x = xpeed就是每執(zhí)行一次RAF(window.requestAnimationFrame),圓心就向右移動(dòng)1像素;

同理可以實(shí)現(xiàn)在圓在y軸上的移動(dòng),甚至是x和y軸的同時(shí)移動(dòng),這樣涉及向量的合成知識(shí)了。

圖片來(lái)源:周余飛

現(xiàn)在大伙是不是深刻理解高中和大學(xué)時(shí)學(xué)的看似無(wú)用的解析幾何的妙用啦,自然界物體的運(yùn)動(dòng)規(guī)律不正是遵循著這些迷人的數(shù)學(xué)等式嗎?

好吧,扯遠(yuǎn)了,言歸正傳~~~
小結(jié)一下物體的勻速運(yùn)動(dòng):

1.物體的勻速運(yùn)動(dòng)無(wú)非是改變其在坐標(biāo)軸的值,但是**每次的改變量是不變的**,也就是單位時(shí)間內(nèi)的移動(dòng)距離是不變的,這樣才符合勻速;
2.通過(guò)向量的合成原理,我們可以在canvas畫(huà)布上實(shí)現(xiàn)任意方向的勻速運(yùn)動(dòng)

【linear-motion】


  • 變速運(yùn)動(dòng)
    如同你的知識(shí)積淀一樣,當(dāng)你學(xué)的越多,運(yùn)用的越靈活,你再去get一門(mén)新的技能的時(shí)候,學(xué)習(xí)的速度就不在是勻速運(yùn)動(dòng),而是變速運(yùn)動(dòng)了。

變速運(yùn)動(dòng),本質(zhì)上是物體運(yùn)動(dòng)過(guò)程中速度在變化,也就是以前學(xué)過(guò)的加速度的概念,也就是說(shuō)要想實(shí)現(xiàn)物體變速運(yùn)動(dòng),只需要改變坐標(biāo)軸的值,每次的改變是變化的

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軸移動(dòng)的距離(移動(dòng)原點(diǎn))   
        var ax = 0.5;//設(shè)置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】
看完上面的代碼有沒(méi)有感到很神奇,同樣一段代碼,只需要添加var vx = 0.5xspeed+=vx就可以使物體實(shí)現(xiàn)加速運(yùn)動(dòng);
看完demo后,你有沒(méi)有發(fā)現(xiàn),當(dāng)速度達(dá)到一定程度的時(shí)候,物體給人的感覺(jué)好像是靜止一樣;

【注】
這里給大伙講一個(gè)上面非線性運(yùn)動(dòng)日常例子,也就是籃球的自由落體運(yùn)動(dòng),先給大伙看看演示的效果:

gravity-acceleration

中學(xué)物理都有學(xué)過(guò),物體在自由落體過(guò)程中受萬(wàn)有引力和空氣摩擦力的合力——重力的影響而向下運(yùn)動(dòng),球體在落地地面給了物體一個(gè)作用力導(dǎo)致物理受向上的力和萬(wàn)有引力的影響而向上運(yùn)動(dòng),循環(huán)反復(fù),由此出現(xiàn)上面的運(yùn)動(dòng)效果;

數(shù)理分析上,物體先是做向下的加速運(yùn)動(dòng),落到地面后(有一個(gè)落到地面的速度)再做向上的減速運(yùn)動(dòng)知道速度為0時(shí),再做向下的加速運(yùn)動(dòng),循環(huán)反復(fù),知道小球落到地面;

<script>

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

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


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

        var vy = 0;
        var gravity = 0.05;//定義重力加速度;
        var bounce = -0.8;//定義反彈系數(shù);

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

                //小球碰到地面時(shí),讓球的位置暫時(shí)設(shè)置為在地面上
                ball.y = canvas.height - ball.radius;

                //此時(shí)設(shè)置小球落到地面時(shí)的速度為反向,大小為原來(lái)的0.8;
                vy *= bounce;
            }
        }

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

            //小球首先做向下的加速運(yùn)動(dòng)
            vy += gravity;
            ball.y += vy;

            //碰撞測(cè)試,當(dāng)小球下落到地面時(shí),vy *= bounce;
            //此時(shí)小球在地面時(shí)的初始速度為vy *= bounce(vy此時(shí)是負(fù)值),接著繼續(xù)向上運(yùn)動(dòng),每渲染1幀,vy+=gravity,注意此時(shí)小球做向上的減速運(yùn)動(dòng),直到速度為0時(shí);
            //接著小球繼續(xù)做向下加速運(yùn)動(dòng),循環(huán)往復(fù),直到小球停止;
            checkGround(ball)

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

    </script>

【gravity-acceleration】

各位可以嘗試去修改gravity和bounce的參數(shù),你會(huì)有意想不到的結(jié)果發(fā)現(xiàn);


  • 波形運(yùn)動(dòng)
    波形運(yùn)動(dòng),顧名思義像波浪般的運(yùn)動(dòng),運(yùn)動(dòng)軌跡如同下面的三角函數(shù)一般;
    此時(shí),我們不禁會(huì)想:要實(shí)現(xiàn)波形運(yùn)動(dòng)的軌跡,是不是需要用到三角函數(shù)呢?
    Bingo,答對(duì)了~~~可是知識(shí)已經(jīng)還給我敬愛(ài)的老師嘞;
圖片來(lái)源:周余飛

別緊張,其實(shí)實(shí)現(xiàn)這個(gè)軌跡so easy,聽(tīng)我娓娓道來(lái)······

先分析一下思路:
實(shí)現(xiàn)圓按照波形運(yùn)動(dòng)的動(dòng)畫(huà)原理是什么?
每16ms瀏覽器渲染1幀,每1幀圓的圓心位置發(fā)生改變,改變的路徑就是這個(gè)正弦函數(shù);

waving-motion

還不懂?答案就是跟前面的代碼一模一樣,只不過(guò)x軸的變化值由


//勻速運(yùn)動(dòng)
ball.x = xspeed//xspeed = 1一直都是1;

//變速運(yùn)動(dòng)
var ax = 0.05;
ball.x += xspeed //xspeed = 1初始值為1;
xspeed += ax//每16ms,xspeed增加0.05;
【注】
各位童鞋自己想一下曲線運(yùn)動(dòng)如何實(shí)現(xiàn)?提示一下,結(jié)合勻速運(yùn)動(dòng)和變速運(yùn)動(dòng)一起思考;
[【curve-motion】](http://terenyeung.applinzi.com/newapp/canvas/html/curve-motion.html)

//波形運(yùn)動(dòng)
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】

細(xì)心的童鞋可能已經(jīng)發(fā)現(xiàn)這么一個(gè)規(guī)律:物體的運(yùn)動(dòng)軌跡無(wú)非就是通過(guò)改變物體在canvas坐標(biāo)軸上的值+RAF這個(gè)API而產(chǎn)生運(yùn)動(dòng)的;

勻速運(yùn)動(dòng)設(shè)置ball.x += 1,每頻次圖形的x軸右移1px;

變速運(yùn)動(dòng)設(shè)置ball.x += vx, vx += ax,每頻次圖形x軸右移vx后,vx加ax,下一次圖形將移動(dòng)vx+ax從而實(shí)現(xiàn)變速;

波形運(yùn)動(dòng)則設(shè)置ball.y = centerY + Mathsin(angle)*swing,由于正弦函數(shù)的值區(qū)間為[-1,1],所以圖形會(huì)永遠(yuǎn)在[centerY-swing,centerY+swing]上下移動(dòng);

這一種思想將會(huì)對(duì)后面的圖形運(yùn)動(dòng)的思考同樣奏效;


  • 圓形運(yùn)動(dòng)
    現(xiàn)在我們?cè)傧胍幌拢绾巫寛A圍繞一個(gè)點(diǎn)做圓周運(yùn)動(dòng)?
    我們學(xué)到的解析幾何有什么是可以表示圓的?相信各位童鞋已經(jīng)學(xué)會(huì)開(kāi)始搶答了,對(duì)啦就是
x*x+y*y = r*r//這是一原點(diǎn)為圓心,半徑為r的圓;

或許有童鞋會(huì)問(wèn)候我尼瑪,你剛才不是告訴我實(shí)現(xiàn)物體運(yùn)動(dòng),只要按照RAF改變物體坐標(biāo)軸的值就行了嗎,你給我上面這么一個(gè)等式,那我怎么樣去給ball.x和ball.y賦值;

人類(lèi)一思考,上帝就發(fā)笑,這是小羊?qū)戇@篇文章時(shí)新鮮看到的一句話,我一開(kāi)始的理解為嘲諷人類(lèi)的自作聰明,后來(lái)想一下我更加愿意理解為上帝是在對(duì)人類(lèi)不斷追求真理這一行為的勉勵(lì)把;

circular-motion

如果有看官想到這一層面,我會(huì)覺(jué)得你很牛X,因?yàn)槲沂鞘潞髲?fù)習(xí)才想到這一點(diǎn)的。不賣(mài)關(guān)子,大家應(yīng)該聽(tīng)說(shuō)過(guò)極坐標(biāo)把(再一次驗(yàn)證原理的有效性)

//圓的極坐標(biāo)表達(dá)式為
x = rcosθ
y = rsinθ

也就是說(shuō)給我一個(gè)圓的半徑和每次旋轉(zhuǎn)的角度,我就可以用x和y的方式描繪圓的路徑

二話不說(shuō)上代碼:

<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】

有了圓形運(yùn)動(dòng),再講一下橢圓運(yùn)動(dòng),思考過(guò)程和上面基本一樣,數(shù)學(xué)表達(dá)式為:

(x/a)*(x/a)+(y/b)*(y/b)=1
//極坐標(biāo)
x = a*cosθ
y = b*sinθ

有了這兩個(gè)坐標(biāo),圖形的橢圓路徑還不出來(lái)嗎,相信你已經(jīng)躍躍欲試了,我這里就直接給demo啦。

ellipse-motion

【ellipse-motion】

其實(shí),圓形運(yùn)動(dòng)本質(zhì)上就是特殊的橢圓運(yùn)動(dòng),各位可以看一下二者的聯(lián)系與區(qū)別:

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

//橢圓運(yùn)動(dòng)
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;

轉(zhuǎn)動(dòng)的方向

動(dòng)畫(huà)特效當(dāng)中其中有一個(gè)很重要的點(diǎn)就是物體的轉(zhuǎn)動(dòng)方向問(wèn)題,以自然界的實(shí)例來(lái)看,你會(huì)看到地球自轉(zhuǎn)及其圍繞太陽(yáng)公轉(zhuǎn);

self-rotation
resolution

這里先給上一段實(shí)現(xiàn)封裝好的Arrow類(lèi),用于后面的講解所用;

//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();
};

小羊在轉(zhuǎn)動(dòng)的方向這一部分要使用一個(gè)canvas的新API——context.rotate(angle)來(lái)控制物體的轉(zhuǎn)動(dòng);

到現(xiàn)在只要你掌握前面所講的動(dòng)畫(huà)原理的話,那么就不難推理出自轉(zhuǎn)和公轉(zhuǎn)的動(dòng)畫(huà)來(lái);

自轉(zhuǎn):每16ms變化一次angle,那么angle作為參數(shù)每傳遞1次,物體就會(huì)轉(zhuǎn)動(dòng)1次,最終形成自轉(zhuǎn)

 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】

公轉(zhuǎn):使用圓周運(yùn)動(dòng)的方法實(shí)現(xiàn)公轉(zhuǎn);
【resolution】

上面的angle的賦值是機(jī)械式,如果我們想要鼠標(biāo)轉(zhuǎn)到哪里,箭頭就指到哪里,會(huì)不會(huì)更加具有交互性;

物體轉(zhuǎn)動(dòng)的角度和鼠標(biāo)的指向有關(guān),那么如何建立二者之間的聯(lián)系呢?

圖片來(lái)源:周余飛

上圖給出了答案:先是獲取到鼠標(biāo)在canvas上的坐標(biāo),然后獲取到物體中心的坐標(biāo),根據(jù)二點(diǎn)間的距離公式,可以測(cè)算出鼠標(biāo)距離中心點(diǎn)在x軸和y軸的分量dx和dy,然后通過(guò)一個(gè)很牛掰的三角函數(shù),

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

這個(gè)三角函數(shù)作用是給它兩個(gè)x和y軸的距離分量,就可以測(cè)算出鼠標(biāo)與x軸的夾角來(lái);
有同學(xué)會(huì)問(wèn):?jiǎn)柺裁纯梢赃@樣?這個(gè)暫時(shí)無(wú)法回答,這個(gè)問(wèn)題深究下去就不屬于本筆記范圍之內(nèi)了,知道有這么一個(gè)方法就okay啦;

測(cè)算出角度,就可以給context.rotation(angle)傳參啦,此時(shí)箭頭將會(huì)跟著鼠標(biāo)轉(zhuǎn)動(dòng);

 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,現(xiàn)在有了轉(zhuǎn)動(dòng),再加入先前的物體運(yùn)動(dòng)就可以讓"讓子彈飛"啦~~~
【cursor-follow】

這個(gè)demo是一個(gè)小飛機(jī),你按下啥鍵它就會(huì)飛向哪,真正實(shí)現(xiàn)和用戶交互;
【spaceShip】

【注】
Math.atan2(dy,dx)函數(shù)很重要!!!,這意味著這要你能夠測(cè)算出鼠標(biāo)與指定點(diǎn)之間的x軸和y軸的分量,那么你就可以獲取到鼠標(biāo)與指定點(diǎn)的連線與x軸所形成的的夾角,由此就可以去改變物體的運(yùn)動(dòng)或是轉(zhuǎn)向;

縮放的比例

canvas提供縮放功能的API可以讓我們對(duì)物體進(jìn)行縮放大小,如果結(jié)合我們之前學(xué)的一些解析幾何的知識(shí),那么就可以創(chuàng)作出千變?nèi)f化的縮放特效出來(lái);

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】


總結(jié)

本篇文章題目是《基于Canvas的動(dòng)畫(huà)基本原理與數(shù)理分析》,因此只介紹了一些在使用canvas元素繪制動(dòng)畫(huà)時(shí)運(yùn)用到的一些常用的解析幾何原理和相關(guān)的物理知識(shí),例如勻速運(yùn)動(dòng)、變速運(yùn)動(dòng)、圓周運(yùn)動(dòng)、波形運(yùn)動(dòng)、脈沖運(yùn)動(dòng),這些運(yùn)動(dòng)過(guò)程中可涉及到的概念又包括向量的分解(力的分解)、重力、摩擦力、加速度、三角函數(shù)等等······

物體的屬性——在canvas上的位置、方向和比例的變化萬(wàn)千無(wú)非是使用上述的數(shù)學(xué)等式通過(guò)RAF從而形成動(dòng)畫(huà)效果的;

當(dāng)然,僅僅掌握這些并不足以讓你設(shè)計(jì)出相當(dāng)出彩的動(dòng)畫(huà)特效,但是就像開(kāi)頭所說(shuō)的這部分的知識(shí)點(diǎn)相當(dāng)于是動(dòng)畫(huà)的基本原理,它相對(duì)于直接使用CSS3做動(dòng)畫(huà)來(lái)的艱深復(fù)雜些,短期可能會(huì)花我們一些時(shí)間去理解,但是往后保不準(zhǔn)會(huì)排上用場(chǎng),反正我在學(xué)習(xí)的過(guò)程中就充滿力量感——因?yàn)槟岈敻咧袑W(xué)的物理和大學(xué)學(xué)的高數(shù)終于排上用場(chǎng)啦!!!

canvas動(dòng)畫(huà)的知識(shí)點(diǎn)還包括一些具體的應(yīng)用和晉升到3D的層次,這一部分的內(nèi)容就留給童鞋們自行解決,冥冥之中我有種預(yù)感,你只要把上面的掌握住了,高級(jí)的就不成問(wèn)題了;

寫(xiě)完這篇文章,感覺(jué)自己要吐血三升!!!具體是多少字?jǐn)?shù),自己也沒(méi)數(shù),可能會(huì)比較長(zhǎng),如果有看官耐住性子看到這里,我可能會(huì)很佩服你喲······

最后,要感謝周余飛童鞋的技術(shù)博客的指導(dǎo),本篇文章是學(xué)習(xí)周余飛同學(xué)【每周一點(diǎn)canvas動(dòng)畫(huà)系列】的學(xué)習(xí)心得和思考,沒(méi)有作者的辛勤付出,也就沒(méi)有我的知識(shí)汲取后的再度分享,為此我在下面給出作者于【github】的地址,有興趣的同學(xué),可進(jìn)一步學(xué)習(xí)和探討,THX。

參考資料
【周余飛——每周一點(diǎn)canvas動(dòng)畫(huà)系列】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 看了很多視頻、文章,最后卻通通忘記了,別人的知識(shí)依舊是別人的,自己卻什么都沒(méi)獲得。此系列文章旨在加深自己的印象,因...
    DCbryant閱讀 736評(píng)論 0 2
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,200評(píng)論 4 61
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫(huà)全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,551評(píng)論 6 30
  • 日更第15篇堅(jiān)果姐姐在345會(huì)上分享了結(jié)構(gòu)化寫(xiě)作之美,她上臺(tái)的第一句話就是,我是為兔子而來(lái)的,為兔子來(lái)做這個(gè)分享。...
    羽青閱讀 1,000評(píng)論 1 13
  • 感覺(jué)是一個(gè)新的開(kāi)始,有了許多輸入,第一次有輸出,期待和緊張,還有小小的盼望,好想以后每天都跑步啊,變瘦一點(diǎn),變成...
    aec75d286fa4閱讀 242評(píng)論 0 1