話說為什麼筆者我要求虐去研究什麼貝塞爾曲線?講真,我一個數學一般般,高數及格飄過的人為什麼要求虐去搞數學公式啊!研究完貝塞爾曲線,我突然想好好學習數學。真的是數學不好,學什麼程式設計啊。(哭暈在草稿紙中……)
正片乾貨在此:
科普時間
提到貝塞爾曲線,大家第一反應是什麼?
學習CSS的小夥伴應該會知道一個叫做animation-timing-function:cubic-bezier(x1,y1,x2,y2)
的引數,用於CSS動畫時間的引數。如果無法理解,就假象下勻速運動和變速運動的。如果還是沒感覺,就想象你在跑步機上跑步,1小時內,有時用8KM/h的速度,有時候用10KM/h的速度。也就是animation-timing-function:cubic-bezier(x1,y1,x2,y2)
的意思就是讓你在一定時間內,用不同的速度運動(運動方式不限,可以是平移,旋轉,拉伸……)。
但是貝塞爾曲線,既然是曲線,一開始並不是用於時間函式的,而是真的用來畫曲線的,比如PS中的鋼筆工具。(驚喜不驚喜,意外不意外,此處應該有表情包。)
PS鋼筆工具(貝塞爾曲線的應用)Bézier curve的定義
A Bézier curve is defined by a set of control points P0 through Pn, where n is called its order (n = 1 for linear, 2 for quadratic, etc.). The first and last control points are always the end points of the curve; however, the intermediate control points (if any) generally do not lie on the curve. The sums in the following sections are to be understood as affine combinations, the coefficients sum to 1.
筆者的渣翻譯:一條貝塞爾曲線是由一組Points從P0~PN所控制的,這邊N就是他的順序(比如N=1的時候是線性的,2的時候是二次,等等)。第一個控制點和最後一個控制點是曲線的終點;然而中間的一些控制點(如果有),通常不在曲線上。這些點的組合可以理解為仿射組合(affine combination,也就是不僅有點,還有點指向的方向),他們的係數之和等於一。
通俗解釋:
- 貝塞爾曲線是由
一堆點
的集合繪製而成。 - 這
一堆點
是在定義的P0~PN的控制之下得出的。 - P0~PN這些定義的點,第一個點和最後一個點是曲線的開頭和結尾。
一條曲線的獲得過程真不容易,也就是說在計算機中曲線的獲得過程並不一帆風順,並不像我們徒手畫一條曲線那麼簡單。如果大家畫過素描,應該知道一個圓應該怎麼畫。也許有人會說,圓這麼簡單,徒手就是一個大餅。對此只能說少年你太簡單了。素描的圓並不是一蹴而就,而是不斷地切割,通過線段慢慢地得出一個圓。當然這只是一個比喻,計算機中的曲線是通過無數的線段組合而成的。
Bézier curve的例項
假設我們將曲線分為10段,貝塞爾曲線就是通過P0~N個點控制,從P0出發,在P0~N這些點的N-1條連線中尋找線段1/10處的點,再連結新的點得出N-2條連線,尋找新得出的線段中1/10處的點,如此迴圈,直至只剩兩點一線,在這條最終的線上尋找一個最終點,也就是組成曲線的點。然後查詢2/10處的點,初次迴圈,直至到達PN。
是不是有點懵,一條曲線的誕生之路真艱辛。來!讓我們通過例項來feel一下。我們是如何通過定義幾個點來控制一條曲線的。
線性Bézier curves
線性Bézier curves是由兩個點P0和P1控制形成的,這個是最簡單的,就是初中(也許是小學了)學的一次函式。大家也許會質疑為什麼我要解釋這麼簡單的問題,筆者你是不是傻了。(放開我,我沒瘋,我還可以繼續。)上一節提到了曲線其實是由無數的線段組成的,因此這個線性的Bézier curve當然就是基礎啦!
二次Bézier curves
好了離開了一次函式,我們要進入二次函式了。二次Bézier curves是由三個點P0,P1和P2組成的。從這裡開始,我們就要開啟新世界的大門了,通過上一節簡單的線性Bézier curves我們開始推導二次Bézier curves的作圖方式以及數學公式。
公式推導:
三次Bézier curves
終於來到了CSS中animation-timing-function:cubic-bezier(p1x,p1y,p2x,p2y)
所需要的曲線了。這個曲線,我們可以通過上述的二次依葫蘆畫瓢畫出來,不同的是動態的線段又多了兩條。
公式推導:
這個分解圖畫起來,有點凶殘,所以筆者做了一個Canvas動畫
線上enjoy的地址:codepen
如果是隻是使用,我們可以通過一個作弊網站獲取到常用的時間曲線引數。
三次Bézier curves和CSS的時間函式的關係
相信大家都發現了上文提到的CSS中的animation-timing-function:cubic-bezier(x1,y1,x2,y2)
這個屬性,其實就是三次貝塞爾曲線的一個應用,不過裡的第一個點和最後一個點的固定的,可以調節的之後P1和P2。
雖然繪製貝塞爾曲線不難,只要理解了其原理,畫一個曲線相信都難不倒大家。但是CSS的時間函式真的難解,因為我們通常是通過時間t,來得出(x,y)的座標,從而繪製曲線,但是在CSS的時間函式中,我們使用的可不是這個方式哦。而是通過已知的x,求出y的值。這裡的難點在於,需要求解一個3元一次方程
(有興趣的可以去解三元一次方程,得出t,在帶入公式得到y)。
也有大神做了這個網站供我們玩轉貝塞爾曲線函式,這樣就不用自己去解三元一次方程了。
遞迴搞定所有型別貝塞爾曲線
雖然我們可以匯出公式來計算貝塞爾曲線的每個點的位置,但是我們可以用另一種更加暴力的方式來完成,也更加直觀。
既然貝塞爾曲線是直線截出來,那麼我就可以用遞迴一層層回撥到只剩一個點,然後根據t再計算新的點,連線這些點我的曲線就形成啦!
每一個貝塞爾曲線都是由線性遞迴而來,那麼先寫一個線性的公式。
function linearBezierCurze(a, b, c, d){
//(a,b),(c,d)
let s1 = c - a, s2 = d - b
return function (t) {
return [
s1 * t + a,
s2 * t + b
]
}
}
複製程式碼
接著對定位點們利用reduce計算兩點之間的新的點,直至新的點只剩一個。
function drawPoints(point){
let newPoint=[]
point.reduce((p,c)=>{
newPoint.push(linearBezierCurze(...p,...c)(t))
return c
})
if(newPoint.length===1){
return newPoint[0]
}else{
return drawPoints(newPoint)
}
}
複製程式碼
筆者寫了一個線上play的demo,大家可以加多點玩,寫的比較簡陋,不要嫌棄:demo
總結
筆者溜了溜了……