看這篇文章之前,請確保你有基本的前端知識,知道Canvas最基本的使用方法,知道貝塞爾曲線在CSS3中的使用。本文章忽略了貝塞爾曲線的歷史,不了解可自行谷歌百度。
三次貝塞爾曲線方程
推導過程
先看一下貝塞爾曲線的生成方法
二次貝塞爾曲線
三次貝塞爾曲線
Cubic Bezier Curve
四次貝塞爾曲線
這是二次貝塞爾曲線、三次貝塞爾曲線、以及四次貝塞爾曲線的生成示意圖,我們可以用平實的文字總結(jié)一下這些圖形表達的含義:對于給定的曲線起點與終點以及一系列控制點,分別將起點與第一控制點,第一控制點與第二控制點,以此類推直至最后一個控制點與終點相連,然后分別將這些線段對應(yīng)的相同比例部分的點相連,記此比例為t,再將這些二次連線上t位置的點相連,依次類推直到不存在第二條線段,最后這條線段t位置上的點就在貝塞爾曲線上,而當t∈[0,1]時這些點的集合就是貝塞爾曲線本身。
三次貝塞爾曲線就是如此計算的:
Q0:P0 + (P1-P0)t Q1:P1 + (P2-P1)t Q2:P2 + (P3-P2)t
A0:Q0 + (Q1-Q0)t A1:Q1 + (Q2-Q1)t
B(t) = A0 + (A1-A0)t
將所有點換成使用P0P1P2P3的表達式進行化簡,即可化簡為
B(t) = P0(1-t)3 + 3P1t(1-t)2 + 3P2t2(1-t) + P3t3
化簡過程涉及到換元,目的是抽象貝塞爾曲線的通用表達式,所以結(jié)果不是那么直觀,不過這些都是數(shù)學細節(jié),感興趣可以去維基百科,平常接觸最多的就是三次貝塞爾曲線,所以我們這里著重探討的就是特殊的三次貝塞爾曲線而非普遍結(jié)論。
這一表達式中的變量t就是我們說的“各條線段上的比例”,在這里我們直接使用了一個字面量(如P0)來表示一個點,實際上編程時我們會將點分成xy坐標分別進行計算。

這里我將貝塞爾曲線的生成方法實現(xiàn)成了一個使用canvas繪制的過程例子,你可以直接拖動滑塊改變描線速度。
運動位置計算
給定t拿到運動位置并非難事,因為我們已經(jīng)有了曲線對應(yīng)的函數(shù)表達式,直接代入?yún)?shù)t即可求得x,y坐標。
比如目前我們希望讓物體位于某已知貝塞爾曲線的t位置,則我們需要的位置為
var x = P0x(1-t)3 + 3P1xt(1-t)2 + 3P2xt2(1-t) + P3xt3;
var y = P0y(1-t)3 + 3P1yt(1-t)2 + 3P2yt2(1-t) + P3yt3;
// position handler , e.g.
// oBox.style.transform = `translate(${x}px,${y}px)`;

當然,這里我們要注意的是:我們并沒有在時間維度控制動畫,或者說目前這個沿曲線運動的圓目前能做到的也僅僅是“沿著曲線運動”,我們目前還沒有控制它的勻速、加速等速度模式。
Canvas任意曲線描線動畫
想要實現(xiàn)canvas描線動畫,首先我們應(yīng)該知道canvas動畫的實現(xiàn)方式:不斷的繪制與擦除。既然我們需要的是一個描線動畫,那么我們就可以將研究目標轉(zhuǎn)向另一個問題:如何繪制從起始點開始到達三次貝塞爾曲線上給定一點的曲線線段?

實際上,我們已經(jīng)知道了如何繪制三次貝塞爾曲線:不斷運動的點所構(gòu)成的線,那我們已經(jīng)知道了t的位置,那實際上就是已經(jīng)知道了繪制到給定點時對應(yīng)的Q0,Q1,A0,而Q0與A0就是這段貝塞爾曲線的控制點,而B2便是終點值。
上文中演示貝塞爾曲線的生成就使用了這一方法,點擊查看例子與源碼
CSS3屬性的轉(zhuǎn)化
想知道如何將CSS3中的屬性值轉(zhuǎn)化為JS中的描述邏輯,首先應(yīng)該搞清楚CSS3中的貝塞爾曲線表達式到底是什么。
- 屬性值的含義
我們看到的CSS3中無論是 transition-timing-function 還是 animation-timing-function都是可以支持三次貝塞爾曲線表達式的,形如cubic-bezier(0.2,0.3,0.71,0.43),括號內(nèi)有四個值,我們之前了解到的三次貝塞爾曲線相關(guān)的點需要四個:起點,第一控制點,第二控制點,終點,而我們的CSS3運動函數(shù)的三次貝塞爾曲線表達式把起點與終點分別約定為(0,0)與(1,1),所以我們這個括號里傳的四個參數(shù)分別是第一、二控制點的X坐標、y坐標,即為
cubic-bezier( P1x, P1y, P2x, P2y)
那既然如此我們就清楚了:我們?nèi)魏瘮?shù)表達式中需要的數(shù)據(jù)都齊全了,下面僅需將CSS表達式轉(zhuǎn)化成JS表達式就好,我們的方法接受CSS3貝塞爾曲線表達式,返回值為接受x為參數(shù)返回值為對應(yīng)y值的方法。
點擊查看CSS屬性轉(zhuǎn)化
本例子實現(xiàn)了以下效果:將CSS3的屬性轉(zhuǎn)化成對應(yīng)的三次貝塞爾曲線。那現(xiàn)在有一個問題:我們?nèi)绾问褂眠@個貝塞爾曲線作為我們所做動畫的時間變化函數(shù)?
時間比例函數(shù)
實際上,我們在這里有三個變量:t,x,y,根據(jù)t與四個已知點我們可以獲得對應(yīng)點的xy值,但是這個等式對我們使用它作為時間函數(shù)并沒有任何作用,我們需要的是:將x作為自變量勻速遞增,這時的x的物理意義便是運動時間,而x值在貝塞爾曲線上對應(yīng)的點的y坐標的意義便是物體運動的百分比。那么我們目前面臨的問題便是:給定一個x,如何在某個特定的貝塞爾曲線上計算出對應(yīng)的y值?

實際上我們是沒有辦法直接算出y的,根據(jù)我們已知的函數(shù)方程
x = P0x(1-t)3 + 3P1xt(1-t)2 + 3P2xt2(1-t) + P3xt3 (1)
y = P0y(1-t)3 + 3P1yt(1-t)2 + 3P2yt2(1-t) + P3yt3 (2)
在這兩個函數(shù)表達式中我們已知的變量只有x,所以我們只能通過(1)式反求出此時的比例t,然后再代入(2)式獲取y值。
因為P0P1P2P3都是已知量,將(1)式簡化后是一個t相關(guān)的一元三次函數(shù):
at3 + bt2 + ct + d = 0 // 此多項式為由上式合并同類項后得出,參數(shù)為合并后代數(shù)值
也就是說我們目前已知a,b,c,d,求出t值,并且我們知道t值的取值范圍是t∈[0,1],所以解三次方程就可以得出了。
但是在實踐中有個問題:解三次函數(shù)會涉及到大量的開方相除操作,并沒有現(xiàn)成的解三次方函數(shù)。這里需要一些計算機求值方面的技巧:我們可以通過二分法去逼近這個結(jié)果。因為我們的t值是[0,1]區(qū)間的,所以我們可以嘗試二分解決,而二分法的精度是1/2^n,當n=10時,這個精度最大值便已經(jīng)是1/1024≈0.001了,這對我們來說已經(jīng)比較精確,但是還有方法可以將這個精度提升:
- 提高n的次數(shù):n=20時這個精度值便為0.000001了,對于我們進行求值計算已經(jīng)十分精確。
- 先控制區(qū)間,然后再在區(qū)間內(nèi)進行二分逼近,比如先將t分成十份,然后將t對應(yīng)的x值與目標值進行逐個比較,確定區(qū)間后進行二分操作,都可以。
除了二分法外,進行曲線上求值還有一個更為高效的方法,叫做牛頓迭代法,是根據(jù)曲線上某點坐標及其導數(shù)進行區(qū)間推導的過程,理解起來復雜程度較高,但是在適用條件內(nèi)逼近精度平方級遞增,效率奇高。想要更多了解相關(guān)知識的同學可以看一下我列的參考資料,或者還是求助萬能的維基百科,我就不多做介紹了。
那沒有具體介紹算法,咋寫?這個比較好說,少年我送你一個庫,這個庫是我fork之后在主體文件上增加了注解。數(shù)學理論和計算機實踐一個很大的不同就是:數(shù)學嚴謹且精確,而計算機計算則需要我們做更多權(quán)衡:精確度和性能,這個庫很棒,能幫助我們理解算法同時也給出了生產(chǎn)解決方案,性能不俗。
那我們下一步就可以嘗試使用這個解決方案來進行時間動畫控制
制作自己的時間函數(shù)控制器
首先我們引入庫,這個庫導出的方法是bezier-curve,這個方法的調(diào)用返回值是一個函數(shù),這個函數(shù)接受x作為參數(shù),返回對應(yīng)的y值,這個例子展示了這個功能,而以x為時間變量,y值的變化就是對應(yīng)的運動函數(shù),在這里我們使用t作為時間變化變量,tSpeed表示t變化快慢,然后通過requestAnimationFrame來不斷進行渲染。

目前我們已經(jīng)實現(xiàn)了一個小的三次貝塞爾曲線運動函數(shù)控制器,下一步我們需要解決的是什么呢?本篇文章未解決的問題,將有另一篇文章給出:
- 如何使用更為復雜的貝塞爾曲線來描述一個多節(jié)動畫函數(shù)?
- 如何控制沿一般曲線運動物體的速度?
- 不借助SVG動畫的幫助,我們?nèi)绾螌崿F(xiàn)物體沿一般復雜曲線運動并使運動物體自動旋轉(zhuǎn)?
參考資料
https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf
https://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
http://math.stackexchange.com/questions/12186/arc-length-of-b%C3%A9zier-curves
http://greweb.me/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
http://stackoverflow.com/questions/29438398/cheap-way-of-calculating-cubic-bezier-length
https://github.com/gre/bezier-easing/blob/master/src/index.js