關(guān)于貝塞爾曲線在前端路徑動畫與動畫函數(shù)上的高級運用

看這篇文章之前,請確保你有基本的前端知識,知道Canvas最基本的使用方法,知道貝塞爾曲線在CSS3中的使用。本文章忽略了貝塞爾曲線的歷史,不了解可自行谷歌百度。

三次貝塞爾曲線方程

推導過程

先看一下貝塞爾曲線的生成方法

二次貝塞爾曲線

Quadratic Bezier Curve
Quadratic Bezier Curve

三次貝塞爾曲線

Cubic Bezier Curve
Cubic Bezier Curve

四次貝塞爾曲線

Cubic Bezier Curve
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坐標分別進行計算。

貝塞爾曲線動態(tài)生成截圖

這里我將貝塞爾曲線的生成方法實現(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任意曲線描線動畫

想要實現(xiàn)canvas描線動畫,首先我們應(yīng)該知道canvas動畫的實現(xiàn)方式:不斷的繪制與擦除。既然我們需要的是一個描線動畫,那么我們就可以將研究目標轉(zhuǎn)向另一個問題:如何繪制從起始點開始到達三次貝塞爾曲線上給定一點的曲線線段?

貝塞爾曲線的截取方法

實際上,我們已經(jīng)知道了如何繪制三次貝塞爾曲線:不斷運動的點所構(gòu)成的線,那我們已經(jīng)知道了t的位置,那實際上就是已經(jīng)知道了繪制到給定點時對應(yīng)的Q0,Q1,A0,而Q0A0就是這段貝塞爾曲線的控制點,而B2便是終點值。

上文中演示貝塞爾曲線的生成就使用了這一方法,點擊查看例子源碼

CSS3屬性的轉(zhuǎn)化

想知道如何將CSS3中的屬性值轉(zhuǎn)化為JS中的描述邏輯,首先應(yīng)該搞清楚CSS3中的貝塞爾曲線表達式到底是什么。

  1. 屬性值的含義

我們看到的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)比較精確,但是還有方法可以將這個精度提升:

  1. 提高n的次數(shù):n=20時這個精度值便為0.000001了,對于我們進行求值計算已經(jīng)十分精確。
  2. 先控制區(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來不斷進行渲染。

配和運動函數(shù)的動態(tài)運動截圖

目前我們已經(jīng)實現(xiàn)了一個小的三次貝塞爾曲線運動函數(shù)控制器,下一步我們需要解決的是什么呢?本篇文章未解決的問題,將有另一篇文章給出:

  1. 如何使用更為復雜的貝塞爾曲線來描述一個多節(jié)動畫函數(shù)?
  2. 如何控制沿一般曲線運動物體的速度?
  3. 不借助SVG動畫的幫助,我們?nèi)绾螌崿F(xiàn)物體沿一般復雜曲線運動并使運動物體自動旋轉(zhuǎn)?
參考資料
  1. http://stackoverflow.com/questions/4089443/find-the-tangent-of-a-point-on-a-cubic-bezier-curve-on-an-iphone

  2. https://pomax.github.io/bezierinfo/

  3. https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf

  4. https://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html

  5. https://medium.com/@ramshandilya/draw-smooth-curves-through-a-set-of-points-in-ios-34f6d73c8f9#.w7sd3hiyc

  6. http://zqdevres.qiniucdn.com/data/20110728232822/index.html

  7. http://bl.ocks.org/hnakamur/e7efd0602bfc15f66fc5

  8. http://math.stackexchange.com/questions/12186/arc-length-of-b%C3%A9zier-curves

  9. http://greweb.me/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/

  10. http://stackoverflow.com/questions/29438398/cheap-way-of-calculating-cubic-bezier-length

  11. https://github.com/gre/bezier-easing/blob/master/src/index.js

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.曲線介紹 貝塞爾曲線(Bézier curve),又稱貝茲曲線或貝濟埃曲線,是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學曲線...
    秦明Qinmin閱讀 7,867評論 2 19
  • 背景: 給一系列頂點,如果只是用直線將其中的各個點依次連接起來,最終形成一個折線圖,這種很容易實現(xiàn)。但是現(xiàn)實...
    狂風無跡閱讀 40,515評論 12 70
  • 一、前言 本系列文章通過介紹 貝塞爾曲線 的基礎(chǔ)知識,貝塞爾曲線在iOS中的應(yīng)用以及一些高級技巧,循序漸進,試圖讓...
    我是王海龍閱讀 4,531評論 4 19
  • 貝塞爾曲線開發(fā)的藝術(shù) 一句話概括貝塞爾曲線:將任意一條曲線轉(zhuǎn)化為精確的數(shù)學公式。 很多繪圖工具中的鋼筆工具,就是典...
    eclipse_xu閱讀 27,987評論 38 370
  • 讓我再看你一遍從頭到尾, 像是被時間蒙住了我的雙眼。 請你再講一遍,關(guān)于那天, 撐著陽傘的姑娘和擦汗的男人。 我知...
    魚耗子閱讀 354評論 0 0

友情鏈接更多精彩內(nèi)容