基于Canvas的动画基本原理与数理分析
轉載自https://www.jianshu.com/p/e70c9cfbdb38
什么是動畫?
就像思考哲學問題無法回避思維和存在的關系一樣,制作動畫同樣無法逃避的問題是動畫的原理是什么?這里提一句題外話,任何原理的東西通常難以讓你短期拾掇成果,但在隱約的未來會起到難以置信的效果,不信就看接下來小羊的一些學習成果分享。
馴龍高手動畫本質上是圖像按照事先設定好的順序在一定的時間內的圖像序列變化運動。
這種圖像序列的變化運動給我們最為直觀的感受就是圖像仿佛真實的在運動一般,由此產生動畫效果。
然后,事實并非如此,真相往往難以用肉眼觀察得到,除非你是上帝~~~
動畫的特性在于:
- 每一張圖像的內容是事先設定好的,內容是不變的,變化的是圖像序列按照規定的順序在變動;
- 構成動畫特效需要在單位時間內渲染一定量的圖像,每張圖像稱之為幀(Frame),通常電影只需要24FPS就足夠流暢,而游戲則需要60FPS,我們設計動畫時通常選用60FPS;
總之,你所看到的動畫無非是你的眼睛在欺騙你的大腦,本質上每一張圖像還是那張圖像,只不過它們在單位時間內按照一定順序在快速移動罷了~~~
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秒鐘整個畫面就會產生動畫效果了。
內容的微調可以是圖形的移動的距離、轉動的方向以及縮放的比例等等,而“捕獲”這些數據的方法就用使用到我們以前忽視的解析幾何的知識了。
移動的距離
- 線性運動
線性運動就是物體朝特定方向的運動,運動過程中速度不發生改變;
這段代碼涉及部分封裝的函數,這里就不講,具體源碼可以參考【github】。
這里主要講解一下思路,如果我們需要圓在x軸上移動,那么一個思路是改變圓的圓心,使圓心在x軸上不斷變化,最終形成動畫的效果;
上面的ball.x = xpeed就是每執行一次RAF(window.requestAnimationFrame),圓心就向右移動1像素;
同理可以實現在圓在y軸上的移動,甚至是x和y軸的同時移動,這樣涉及向量的合成知識了。
圖片來源:周余飛現在大伙是不是深刻理解高中和大學時學的看似無用的解析幾何的妙用啦,自然界物體的運動規律不正是遵循著這些迷人的數學等式嗎?
好吧,扯遠了,言歸正傳~~~
小結一下物體的勻速運動:
【linear-motion】
- 變速運動
如同你的知識積淀一樣,當你學的越多,運用的越靈活,你再去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.5和xspeed+=vx就可以使物體實現加速運動;
看完demo后,你有沒有發現,當速度達到一定程度的時候,物體給人的感覺好像是靜止一樣;
【注】
這里給大伙講一個上面非線性運動日常例子,也就是籃球的自由落體運動,先給大伙看看演示的效果:
中學物理都有學過,物體在自由落體過程中受萬有引力和空氣摩擦力的合力——重力的影響而向下運動,球體在落地地面給了物體一個作用力導致物理受向上的力和萬有引力的影響而向上運動,循環反復,由此出現上面的運動效果;
數理分析上,物體先是做向下的加速運動,落到地面后(有一個落到地面的速度)再做向上的減速運動知道速度為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的參數,你會有意想不到的結果發現;
- 波形運動
波形運動,顧名思義像波浪般的運動,運動軌跡如同下面的三角函數一般;
此時,我們不禁會想:要實現波形運動的軌跡,是不是需要用到三角函數呢?
Bingo,答對了~~~可是知識已經還給我敬愛的老師嘞;
別緊張,其實實現這個軌跡so easy,聽我娓娓道來······
先分析一下思路:
實現圓按照波形運動的動畫原理是什么?
每16ms瀏覽器渲染1幀,每1幀圓的圓心位置發生改變,改變的路徑就是這個正弦函數;
還不懂?答案就是跟前面的代碼一模一樣,只不過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]上下移動;
這一種思想將會對后面的圖形運動的思考同樣奏效;
- 圓形運動
現在我們再想一下,如何讓圓圍繞一個點做圓周運動?
我們學到的解析幾何有什么是可以表示圓的?相信各位童鞋已經學會開始搶答了,對啦就是
或許有童鞋會問候我尼瑪,你剛才不是告訴我實現物體運動,只要按照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-rotationresolution這里先給上一段實現封裝好的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-effectball.scaleX = ball.scaleY = centerScale + Math.sin(angle)*swing;//bigger and bigger effect//ball.scaleX = ball.scaleY = centerScale + angleball.draw(context);})();</script>【plusing-motion】
總結
本篇文章題目是《基于Canvas的動畫基本原理與數理分析》,因此只介紹了一些在使用canvas元素繪制動畫時運用到的一些常用的解析幾何原理和相關的物理知識,例如勻速運動、變速運動、圓周運動、波形運動、脈沖運動,這些運動過程中可涉及到的概念又包括向量的分解(力的分解)、重力、摩擦力、加速度、三角函數等等······
物體的屬性——在canvas上的位置、方向和比例的變化萬千無非是使用上述的數學等式通過RAF從而形成動畫效果的;
當然,僅僅掌握這些并不足以讓你設計出相當出彩的動畫特效,但是就像開頭所說的這部分的知識點相當于是動畫的基本原理,它相對于直接使用CSS3做動畫來的艱深復雜些,短期可能會花我們一些時間去理解,但是往后保不準會排上用場,反正我在學習的過程中就充滿力量感——因為尼瑪高中學的物理和大學學的高數終于排上用場啦!!!
canvas動畫的知識點還包括一些具體的應用和晉升到3D的層次,這一部分的內容就留給童鞋們自行解決,冥冥之中我有種預感,你只要把上面的掌握住了,高級的就不成問題了;
寫完這篇文章,感覺自己要吐血三升!!!具體是多少字數,自己也沒數,可能會比較長,如果有看官耐住性子看到這里,我可能會很佩服你喲······
最后,要感謝周余飛童鞋的技術博客的指導,本篇文章是學習周余飛同學【每周一點canvas動畫系列】的學習心得和思考,沒有作者的辛勤付出,也就沒有我的知識汲取后的再度分享,為此我在下面給出作者于【github】的地址,有興趣的同學,可進一步學習和探討,THX。
參考資料
【周余飛——每周一點canvas動畫系列】
作者:犯迷糊的小羊
鏈接:https://www.jianshu.com/p/e70c9cfbdb38
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的基于Canvas的动画基本原理与数理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大学计算机专业绩点在3.5算好,绩点3.
- 下一篇: lucene搜索引擎总结