每周一點canvas動畫代碼文件
在上一篇《每周一點canvas動畫》——從2D到3D中,我們討論了要在2D的平面實現3D的效果,是一件多麼複雜的事情。但是對於一些簡單的3D效果,使用webGL不僅有殺雞用牛刀的感覺,而且瀏覽器的兼容性也是一個很大的問題。所以,我們考慮在2D的canvas中去模擬3D的效果,將其作為我們項目中的降級方案。也許你對在2D的canvas中去模仿3D的效果保有懷疑,這裡我先給一個小小的demo,讓你直觀的感受下,canvas模擬的3D效果到底如何?
是不是很逼真,立體效果不錯吧!我記得前段時間淘寶首頁有一個簡單的3D效果(可是我沒找到,不過有那麼個印象),以為用了webGL。其實,canvas完全就可以模擬。下面我們介紹3D環境的搭建。
1.坐標系統
前面部分的動畫內容之所以是2維的,是因為我們所有的動畫都基於一個2維坐標系統。要實現3維的動畫效果,除了x軸,y軸,我們還需要另一條坐標軸—— z軸。但是,canvas先天是不具備這條坐標軸的。所以,我們需要手工的去設定這條坐標軸。
在坐標軸的設定上,我們有兩種選擇,如下圖所示:
第一種叫做左手坐標系(左手的食指,中指,大拇指三者垂直,大拇指指向自己),第二種叫做右手坐標系(右手的食指,中指,大拇指三者垂直,大拇指指向外面)。他們之間的區別如圖所示,z軸的指向不同。以右手坐標為例,反映到物體的表現上(如果只是考慮物體的大小),當物體朝著z軸的正向運動,那麼我們會看到物體變得越來越小。就如第一幅效果圖中展示的那樣,當物體朝著負方向運動的時候。我們可以看到物體變得越來越大,產生一種朝我們迎面飛來的感覺。另外,在本文中我們默認使用的是右手坐標系。
2.透視(perspective)不管你是叫他景深也好,透視也罷。perspective是3D場景中最重要的概念之一,如果你使用過three.js,就會發現camera中有一個參數,就是perspective。另一個你很熟悉的場景,恐怕就是css3中的perspective了吧!如果你對這其中的任何一個有了解,那麼perspective的概念就很好理解了。
perspective的作用是確定物體是靠近我們,還是遠離我們。因為在2維的空間中,我們只需要兩個坐標就可以確定一個物體在平面上的位置。但是,在3維的環境中這是行不通的,兩個物體可能具有相同的x坐標,y坐標。但是只要z坐標不相同,我們就不能判斷他們兩的位置是不是重合的。
那麼,怎麼在2維的平面去體現透視效果呢?各位看官請試想一下,當籃球從遠處飛向你的時候(這裡如果我們只考慮一個元素——籃球大小),籃球是不是越來越大呢,當你把籃球扔出去,它是不是越來越小呢!Ok,就是這個理。在2維的平面,我們想要讓物體感覺向我們走來,就放大它,同理遠離的效果就是讓它變小。
除了讓它的大小發生變化。另一個比較重要的點是:當物體遠離直至消失的過程中,要想模擬三維的消失效果,我們必須讓物體的x坐標,y坐標向消失點移動。這個消失點你可以理解為汽車向遠方駛去,最終它會逐漸變成黑點消失在地平線上,這個黑點就是消失點。
所以總結下來,我們在perspective這一塊要做兩件事:
放大或者縮小物體
讓它靠近或者遠離消失點
2.2 公式上面的概念中我們了解了要想形成透視的效果,我們需要做兩件事。下圖展示了一個側面視圖,人眼代表觀察點,藍色的球體代表屏幕中的物體,人眼距離屏幕的距離為fl,物體與屏幕之間有一段距離z值(這個值在成像的時候並不存在,反映到物體大小的變化上)
物體的大小與這fl,z兩者之間滿足下面的關係:
scale = fl / (fl + z) = 1 / (1 + z/fl)
fl的值就如css3中我們設定的perspective值。 同理,這裡也是我們自己設定的一個值,200,300,500都無所謂啦。從公式中我們可以得出:scale的值一般情況下範圍為(0,1.0),這個值之後會用在縮放物體的比例與靠近消失點的比例。試想兩種比較極端的情況:當z軸無限大的時候(也就是朝著z軸的正向持續運動),scale的值就會趨近於0。當z的距離趨近於-fl的時候,scale的值就會變得很大,就像是戳進我們的眼裡。
以我們前面使用的小球為例,在draw方法上我們定義
context.scale(this.scaleX, this.scaleY)
當縮放比例確定後,一方面我們確定了球體大小的變化,另一方面我們用物體的坐標乘以這個比例就可以得到物體的新坐標。
可能這樣說比較抽象,我們假定fl=200,這時物體的z坐標等於0,由公式可得scale=1.0,那麼物體的大小不變,物體的位置不變。如果z=200,那麼scale=0.5。物體的大小變為原來的1/2,同時我們要讓它現在的坐標乘以縮放比例scale,得到新的位置。如果原來的為(200,300),那麼新坐標就為(100,150)。具體效果如下圖:
先上效果圖
這裡我們讓小球的位置跟隨滑鼠移動,通過鍵盤的上下鍵控制小球在z軸上的距離。
<canvas id="canvas" width="500" height="400" style="background:#000;"></canvas> <script src="../js/utils.js"></script> <script src="../js/ball.js"></script> <script> window.onload = function(){ var canvas = document.getElementById('canvas'), context = canvas.getContext('2d'), ball = new Ball(40, "red"), mouse = utils.captureMouse(canvas); var xpos = 0, //物體的3D坐標 ypos = 0, zpos = 0, fl = 250, //距離屏幕的距離(焦距) vpX = canvas.width/2, //消失點 vpY = canvas.height/2; window.addEventListener('keydown', function(e){ if(e.keyCode === 38){ //up zpos += 5; }else if(e.keyCode === 40){ zpos -= 5; } }, false); (function drawFrame(){ window.requestAnimationFrame(drawFrame, canvas); context.clearRect(0, 0, canvas.width, canvas.height); if(zpos > -fl){ var scale = fl/(fl + zpos); //縮放比列 xpos = mouse.x - vpX; ypos = mouse.y - vpY; ball.scaleX = ball.scaleY = scale; //物體大小變化 ball.x = vpX + xpos*scale; //新坐標 ball.y = vpY + ypos*scale; ball.visible = true; //物體可見 }else{ ball.visible = false } if(ball.visible){ ball.draw(context); } }()) }
代碼相對來說比較簡單。首先,我們設定物體的3D坐標xpos,ypos,zpos,初始默認為0。然後設定焦距fl = 250,最後設置消失點(vpX, vpY)。這裡需要注意的地方是,我們設置的消失點為畫布的中心。如果不這樣做,物體就會向畫布的左上角(0,0)處匯集,這並不是我們想要的效果。
接下來,在動畫循環中我們根據公式計算縮放比例scale,然後作用於ball的scale上,最後計算物體的新位置。這裡有個值得注意的點是,我們在外層加了一個判定條件(zpos > -fl),這樣做的目的是當物體太大的時候,超出了canvas畫布我們就不再繪製它。
這一節的內容是整個3維效果的核心,後面的所有效果都是基於此。所以請務必弄明白!
原文連結
https://segmentfault.com/a/1190000006614206
原作者
我仍舊在這裡