貝塞爾曲線(Bezier Curve)在計算機圖形領域應用很是普遍,好比咱們熟知的 CSS 動畫、 Canvas 以及 Photoshop 等均可以看到貝塞爾曲線的身影。javascript
貝塞爾曲線於 1962 年,由法國工程師皮埃爾·貝濟埃(Pierre Bézier)所普遍發表,他運用貝塞爾曲線來爲汽車的主體進行設計。java
貝塞爾曲線主要用於二維圖形應用程序中的數學曲線,曲線由起始點,終止點(也稱錨點)和控制點組成,經過調整控制點,經過必定方式繪製的貝塞爾曲線形狀會發生變化。後面會具體介紹繪製的方法。git
在計算機圖形學中貝賽爾曲線的運用很普遍,例如Photoshop中的鋼筆效果,Flash5的貝塞爾曲線工具,在軟件GUI開發中通常也會提供對應的方法來實現貝賽爾曲線,咱們熟知的CSS動畫過渡時間函數也是經過貝塞爾曲線(三階貝塞爾曲線)獲取的。github
貝塞爾曲線根據控制點的數量分爲:segmentfault
下圖爲一個三階的貝塞爾曲線,包括四個控制點,分別爲。數組
那咱們經過控制點是怎麼繪製出貝塞爾曲線的呢?緩存
經過上圖的三階貝塞爾曲線舉例,基本的步驟以下:markdown
經過控制的值,由 0 增長至 1,就繪製出了一條由起點至終點的貝塞爾曲線。函數
你能夠經過下面這個動畫直觀感覺一下繪製的過程:工具
對於一階貝塞爾曲線,咱們能夠經過幾何知識,很容易根據的值得出線段上那個點的座標:
而後能夠得出:
對於二階貝塞爾曲線,其實你能夠理解爲:在上利用一階公式求出點,而後在上利用一階公式求出點,最後在上再利用一階公式就能夠求出最終貝塞爾曲線上的點。具體推導過程以下:
先求出線段上的控制點。
將上面的公式帶入至下列公式中:
得出如下公式:
與二階貝塞爾曲線相似,能夠經過相同的方法得出如下座標公式:
這裏我就直接把階貝塞爾曲線公式給出來了,有興趣的同窗能夠自行研究一下。
即:
公式中的值爲,與統計學有關,有興趣的同窗能夠看一看個人這篇文章。
其中的值爲:
若是要實現一個這樣的三階貝塞爾曲線,咱們須要不只須要獲取到一些曲線上的點,還須要經過x軸獲取y軸座標。
CSS中的easing貝塞爾曲線有一個特色,那就是起點和終點是固定的,也就是分別是。因此未知的點就只有兩個,也就是須要傳入四個值,而且這四個值的範圍須要在內。
因此咱們須要建立一個類CubicBezier,它擁有屬性controlPoints
:
class CubicBezier { constructor(x1, y1, x2, y2) { this.controlPoints = [x1, y1, x2, y2]; } } 複製代碼
經過上述代碼初始化之後,咱們還須要根據t(取值範圍爲)值獲取座標,以及一個曲線上座標集合的數組。另外還須要使用三階貝塞爾公式:
由於點座標爲[0, 0],點座標爲爲因此公式進而能夠寫成:
class CubicBezier { constructor(x1, y1, x2, y2) { this.controlPoints = [x1, y1, x2, y2]; } getCoord(t) { // 若是t取值不在0到1之間,則終止操做 if (t > 1 || t < 0) return; const _t = 1 - t; const [ x1, y1, x2, y2 ] = this.controlPoints; const coefficient1 = 3 * t * Math.pow(_t, 2); const coefficient2 = 3 * _t * Math.pow(t, 2); const coefficient3 = Math.pow(t, 3); const px = coefficient1 * x1 + coefficient2 * x2 + coefficient3; const py = coefficient1 * y1 + coefficient2 * y2 + coefficient3; // 結果只保留三位有效數字 return [parseFloat(px.toFixed(3)), parseFloat(py.toFixed(3))]; } } 複製代碼
利用上述的Bezier類,咱們就能夠根據兩個控制點構建Bezier實例,經過這個實例咱們能夠根據t值,獲取點上的近似值。
那麼若是咱們想要根據x軸座標值,來獲取y軸座標時,咱們該怎麼作呢?
這裏我使用了一個近似處理的辦法,具體以下:
因此咱們須要進一步改造Bezier構造函數,須要緩存固定數量座標數組的屬性coords
,以及獲取coords
的方法getCoordsArray
,最後還有獲取y軸座標的方法getY
,具體的實現方法以下:
class CubicBezier { constructor(x1, y1, x2, y2) { const precision = 100; this.controlPoints = [x1, y1, x2, y2]; this.coords = this.getCoordsArray(precision); } getCoord(t) { // 若是t取值不在0到1之間,則終止操做 if (t > 1 || t < 0) return; const _t = 1 - t; const [ x1, y1, x2, y2 ] = this.controlPoints; const coefficient1 = 3 * t * Math.pow(_t, 2); const coefficient2 = 3 * _t * Math.pow(t, 2); const coefficient3 = Math.pow(t, 3); const px = coefficient1 * x1 + coefficient2 * x2 + coefficient3; const py = coefficient1 * y1 + coefficient2 * y2 + coefficient3; // 結果只保留三位有效數字 return [parseFloat(px.toFixed(3)), parseFloat(py.toFixed(3))]; } getCoordsArray(precision) { const step = 1 / (precision + 1); const result = []; for (let t = 0; t <= precision + 1; t++) { result.push(this.getCoord(t * step)); } this.coords = result; return result; } getY(x) { if (x >= 1) return 1; if (x <= 0) return 0; let startX = 0; for (let i = 0; i < this.coords.length; i++) { if (this.coords[i][0] >= x) { startX = i; break; } } const axis1 = this.coords[startX]; const axis2 = this.coords[startX - 1]; const k = (axis2[1] - axis1[1]) / (axis2[0] - axis1[0]); const b = axis1[1] - k * axis1[0]; // 結果也只保留三位有效數字 return parseFloat((k * x + b).toFixed(3)); } } 複製代碼
而後經過下述方式就可使用咱們的CubicBezier
了:
const cubicBezier = new CubicBezier(0.3, 0.1, 0.3, 1); cubicBezier.getY(0.1); // 0.072 cubicBezier.getY(0.7); // 0.931 複製代碼
我寫了一個應用這個
CubicBezier
構造函數的庫Animate-Scroll,有興趣的能夠去看一下源碼。
一個階貝塞爾曲線能夠經過一個形狀徹底一致的階貝塞爾曲線表示。那咱們該怎麼作,才能獲取這個階貝塞爾曲線呢?
由高階貝塞爾曲線表示低階貝塞爾曲線的過程,咱們稱之爲升階。
咱們須要用到這個等式來作升階。
將如下等式帶入上面這個公式中:
而後得出如下公式:
根據以上結果能夠得出控制點由以前的變成了,,和四個控制點了,從而完成了升階。
這裏須要進行一些推導(這裏的推導須要用到公式,有興趣的同窗能夠本身推導一下),由於:
貝塞爾公式能夠表示爲:
帶入上述兩個等式,得:
由於當時:
因此該式能夠寫成:
又由於:
當時:
因此:
將上述兩個等式(1)和(2)代入公式(0)中,最終能夠得出下面這個升階公式:
關於貝塞爾曲線基本的內容就差很少講完了,若是您發現不正確或者有補充的地方,歡迎在評論裏指出😊。