我是個很懶的人,開發過程中經常有意無意地刻意避開數學相關的知識,你也知道解數學題非常枯燥無趣。平時寫動畫也儘量使用 css3 來實現,timer-function 隨意選用,最多也就調一下 cubic-bezier,找到看著舒服的就行。但是怎樣讓動畫更順滑,寫出更貼近自然的動畫,說實話以前我沒怎麼考慮過。
每次當動效設計師提出,能不能這樣那樣的時候,我會理所當然地予以否決。所以有很長一段時間,我非常羨慕那些能用 canvas 繪製很酷炫的動畫的程式設計師。
先這樣吧,又不是不會動。
今天來分享一下三角函式相關的內容,如果剛學前端的時候有人教我這些,我會很開心。
三角函式
三角函式已經是老生常談了(街舞圈稱之為 Old School),它伴隨我們從初中到大學,太多的公式定理,光是應付考試就花了不少時間。先簡單回顧一下,確保你還記得基礎知識。
勾股定理
最開始學三角函式的時候就是從背勾三股四弦五開始,勾股定理描述的是對於直角三角形,直角兩條邊的平方和等於斜邊的平方,
常用三角函式
印象中教科書裡面只保留了 sin, cos, tan,其他可以通過變換得到。
1 2 3 |
sinθ = a / h cosθ = b / h tanθ = a / b |
極座標系和單位圓
在笛卡爾直角座標系中,任一點 (x, y) 都可以轉化成極座標表示 (r, θ),其中
1 2 |
r = Math.sqrt(x^2 + y^2) θ = Math.atan2(y, x) |
單位圓的定義是半徑為單位長度的圓,圓上任意一點的橫座標就是對應角度的餘弦值,任意點的縱座標就是對應角度的正弦值。
簡單的影象變換
以正弦曲線為例,對函式進行簡單的變換,得到不一樣的結果。
正弦曲線公式:y = A sin(Bx + C) + D
A 控制振幅,A 值越大,波峰和波谷越大,A 值越小,波峰和波谷越小;
B 值會影響週期,B 值越大,那麼週期越短,B 值越小,週期越長。
C 值會影響影象左右移動,C 值為正數,影象右移,C 值為負數,影象左移。
D 值控制上下移動。
這個公式非常有用,如果下文的程式碼讓你不解,記得回來檢視註解。
簡單得回顧一下之後,確保你還記得這些基礎知識,那麼這些曾經被得滾瓜爛熟的內容,和前端程式碼結合會變成什麼樣?
常見的應用場景
影象應用
最直觀的應用是使用三角函式來繪製 Wave 曲線
1 2 3 |
for (let x = 0; x < width; x++) { const y = Math.sin(x * a) * amplitude } |
再結合三角函式偏移讓左右成為波谷,中間成為波峰,就能得到曼妙的波紋。
1 2 3 4 5 |
for (let x = 0; x < width; x++) { const radians = x / width * Math.PI * 2 const scale = (Math.sin(radians - Math.PI * 0.5) + 1) * 0.5 const y = Math.sin(x * 0.02 + xSpeed) * amplitude * scale } |
之前掘金上很火的一篇文章,也是同樣的道理,使用兩層正弦函式繪製曲線,fill 之後加上 stagger 動畫,就能得到非常酷炫的?效果。
如果再結合滑鼠位置 + lerp 動畫,就能實現堅果首頁同款的動畫。
這篇文章大部分程式碼都可以在我的 Codepen 主頁看到。
SlowInSlowOut
正餘弦曲線有很自然地緩入緩出的特性,並且在一個週期裡面從 -1 到 1 再回到 -1,非常適合用來模擬一些物理效果。因為真實世界裡面,汽車都是緩慢啟動,加速,減速,再緩慢減速直到速度變為 0 的,突變會讓人很難受。左邊的擺球是線性勻速擺動,右邊是用了三角函式優化的結果。顯然左邊的效果設計師會打人。
只需使用 sin 或 cos 乘以最大角度,就可以得到在擺動最大角度之間的 SlowInSlowOut。
1 |
ctx.rotate(Math.cos(t / 180 * Math.PI) * Math.PI * 0.25) |
角度控制
在開發過程中,我們常常需要跟角度打交道,比如在頭像左上角(45deg)顯示 Notification 紅點,用滑鼠控制 rotation 等等。
前端 JS 裡面 Math.atan2(y, x) 可以用來計算 (x, y) 和 x 軸正方向的夾角弧度值。
1 2 3 4 5 |
function getCurrentDegree () { const deltaX = mouse.x - window.innerWidth * 0.5 const deltaY = mouse.y - window.innerHeight * 0.5 return Math.atan2(deltaY, deltaX) * 180 / Math.PI } |
插一句,三角函式相關的動畫並不一定需要用 js 來寫,比如下面的 DEMO,使用 compass 依賴,同樣可以做到靈活控制在特定角度的動畫(千萬不要手寫各個點的座標!!!後期沒辦法維護)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@import "compass"; .checkbox:checked { ~ button { $per: 180 / 4; <a href='http://www.jobbole.com/members/lowkey2046'>@for</a> $i from 1 through 6 { &:nth-of-type(#{$i}) { $angle: $per * ($i - 1) * 1deg + 180deg; $x: cos($angle) * $d; $y: sin($angle) * $d; transform: translate($x, $y) rotate(0deg) ; } } } } |
Case Study
經常用到的場景大概就這些吧,再以一個案例分析來複習一下。
前兩天在 Codepen 首頁看到熱門推薦,作者用存 css 動畫來實現一個行走的動畫,挺新穎的,然而仔細一看,腳步的動畫真心覺得彆扭,於是想用三角函式優化一下。
繪製頭部:
1 2 3 4 5 6 7 8 9 |
drawHead (t) { ctx.save() ctx.beginPath() ctx.translate(0, Math.sin(t) * 4) ctx.arc(80, -35, 35, 0, 2 * Math.PI) ctx.fill() ctx.closePath() ctx.restore() } |
我會給每個方法傳入週期引數 t, t 從 0 到 2 PI , 這樣能保證所有的週期運動時間同步。
身體和陰影的繪製都差不多,直接跳過看腳步動畫。
腳有兩隻,按道理應該是抬腳到落腳的動作完成時,其他部位都完成了一個完整的週期,所以在繪製腳的時候,t 需要除以 2。然後第一隻腳和第二隻腳相差半個腳自身的週期,可以直接將 t 替換成 t + Math.PI 就是第二腳的動畫。
1 2 3 4 5 6 7 |
drawFeet (t) { t = t / 2 ctx.translate(Math.cos(t) * -50, 0) // 另一隻腳 ctx.translate(Math.cos(t + Math.PI) * -50, 0) } |
腳步動畫自身週期的一半是在地面上的,可以通過判斷一下 sin 值,小於 0 則不做 y 縱軸方向上的變化。
1 |
ctx.translate(Math.cos(t) * -50, Math.sin(t) > 0 ? Math.sin(t) * -35 : 0) |
還沒完,為了讓腳更加逼真,同樣在前半個週期做一下 rotate 。
1 2 3 |
if (t < Math.PI) { ctx.rotate(Math.sin(t) * Math.PI / 180 * -5) } |
最終得到的效果是這樣的:
總結
現如今前端發展速度非常迅速,剛入門的同學剛學完 jQuery 就被告知,You Dont Need jQuery。追新的指令碼根本停不下來,在學習新框架新技能的同時,也別忘了基礎知識的重要性。
好了,今天就分享到這裡,希望一次彙集這麼多效果,能讓你下次使用三角函式更得心應手。
以上大部分程式碼都可以在我的 Codepen 主頁看到。