成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

用canvas繪制一個(gè)曲線動(dòng)畫(huà)——深入理解貝塞爾曲線

xiaowugui666 / 708人閱讀

摘要:實(shí)現(xiàn)這樣一個(gè)曲線動(dòng)畫(huà)可以點(diǎn)擊這里查看在線演示在寫(xiě)代碼之前,先了解一下什么是貝塞爾曲線吧。繪制二次貝賽爾曲線路徑這樣就完成了基本的繪制二次貝塞爾曲線的方法了。

我的github博客地址 https://github.com/hujiulong/...
前言

在前端開(kāi)發(fā)中,貝賽爾曲線無(wú)處不在:

它可以用來(lái)繪制曲線,在svg和canvas中,原生提供的曲線繪制都是使用貝賽爾曲線

它也可以用來(lái)描述一個(gè)緩動(dòng)算法,設(shè)置css的transition-timing-function屬性,可以使用貝塞爾曲線來(lái)描述過(guò)渡的緩動(dòng)計(jì)算

幾乎所有前端2D或3D圖形圖表庫(kù)(echarts,d3,three.js)都會(huì)使用到貝塞爾曲線

這篇文章我準(zhǔn)備從實(shí)現(xiàn)一個(gè)非常簡(jiǎn)單的曲線動(dòng)畫(huà)效果入手,幫助大家徹底地弄懂什么是貝塞爾曲線,以及它有哪些特性,文章中有一點(diǎn)點(diǎn)數(shù)學(xué)公式,但是都非常簡(jiǎn)單:)。

實(shí)現(xiàn)這樣一個(gè)曲線動(dòng)畫(huà)

可以點(diǎn)擊這里查看在線演示

在寫(xiě)代碼之前,先了解一下什么是貝塞爾曲線吧。

貝塞爾曲線

貝塞爾曲線(Bezier curve)是計(jì)算機(jī)圖形學(xué)中相當(dāng)重要的參數(shù)曲線,它通過(guò)一個(gè)方程來(lái)描述一條曲線,根據(jù)方程的最高階數(shù),又分為線性貝賽爾曲線,二次貝塞爾曲線、三次貝塞爾曲線和更高階的貝塞爾曲線。

下面詳細(xì)介紹一下用得比較多的二次貝塞爾曲線和三次貝塞爾曲線

二次貝塞爾曲線

二次貝塞爾曲線由三個(gè)點(diǎn)P0,P1,P2來(lái)確定,這些點(diǎn)也被稱(chēng)作控制點(diǎn)。曲線的方程為:

這個(gè)方程其實(shí)有它的幾何意義,它表示可以通過(guò)這樣的步驟來(lái)繪制一條曲線:

選定一個(gè)0-1t

通過(guò)P0P1計(jì)算出點(diǎn)Q0Q0P0 P1連成的直線上,并且length( P0, Q0 ) = length( P0, P1 ) * t

同樣,通過(guò)P1P2計(jì)算出Q1,使得length( P1, Q1 ) = length( P1, P2 ) * t

再重復(fù)一次這個(gè)步驟,通過(guò)Q1Q2計(jì)算出B,使得length( Q0, Q1 ) = length( Q0, B ) * t。B就為當(dāng)前曲線上的點(diǎn)

注:上面的length表示兩點(diǎn)之間的長(zhǎng)度

圖:二次貝塞爾曲線結(jié)構(gòu)

有了曲線方程,我們直接代入具體的t值就能算出點(diǎn)B了。

如果將t的值從0過(guò)渡到1,不斷計(jì)算點(diǎn)B,就可以得到一條二次貝塞爾曲線:

圖:二次貝塞爾線繪制過(guò)程

在canvas中,繪制二次貝塞爾曲線的方法為

ctx.quadraticCurveTo( p1x, p1y, p2x, p2y )

其中p1x, p1y, p2x, p2y為后兩個(gè)控制點(diǎn)(P1P2)的橫縱坐標(biāo),它默認(rèn)將當(dāng)前路徑的起點(diǎn)作為一個(gè)控制點(diǎn)(P0)。

三次貝塞爾曲線

三次貝塞爾曲線需要四個(gè)點(diǎn)P0,P1,P2,P3來(lái)確定,曲線方程為

它的計(jì)算過(guò)程和二次貝塞爾曲線類(lèi)似,這里不再贅述,可以看下圖:

圖:三次貝塞爾曲線結(jié)構(gòu)

同樣,將t的值從0過(guò)渡到1,就可以繪制出一條三次貝塞爾曲線:

圖:三次貝塞爾曲線繪制過(guò)程

在canvas中,繪制三次貝塞爾曲線的方法為

ctx.bezierCurveTo( p1x, p1y, p2x, p2y, p3x, p3y )

其中p1x, p1y, p2x, p2y, p3x, p3y為后三個(gè)控制點(diǎn)(P1,P2P3)的橫縱坐標(biāo),它默認(rèn)將當(dāng)前路徑的起點(diǎn)作為一個(gè)控制點(diǎn)(P0)。

貝塞爾曲線的特征

在三次貝塞爾曲線后面,還有更高階的貝塞爾曲線,同樣它們繪制的過(guò)程也更加復(fù)雜

四次貝塞爾曲線

圖:四次貝塞爾曲線

五次貝塞爾曲線

圖:五次貝塞爾曲線

我們可以歸納出貝塞爾曲線有幾個(gè)重要的特征:

n階貝塞爾曲線需要n+1個(gè)點(diǎn)來(lái)確定

貝塞爾曲線是平滑的

貝塞爾曲線的起點(diǎn)和終點(diǎn)與對(duì)應(yīng)控制點(diǎn)的連線相切

繪制貝塞爾曲線

復(fù)習(xí)完基礎(chǔ)概念,接下來(lái)就要講如果繪制貝塞爾曲線啦

為簡(jiǎn)單起見(jiàn),我們選擇使用二次貝塞爾曲線

我們先不考慮動(dòng)畫(huà)的事,我們先將問(wèn)題簡(jiǎn)化成:給定一個(gè)起點(diǎn)和一個(gè)終點(diǎn),需要實(shí)現(xiàn)一個(gè)函數(shù),它能夠繪制出一條曲線。

也就是說(shuō)我們需要實(shí)現(xiàn)一個(gè)函數(shù)drawCurvePath,除渲染上下文ctx外(不清楚ctx是什么的同學(xué)可以先熟悉下canvas的基本概念),它接受三個(gè)參數(shù),分別為二次貝塞爾曲線的三個(gè)控制點(diǎn)。我們將樣式控制移到函數(shù)外,drawCurvePath只用來(lái)繪制路徑。

/**
 * 繪制二次貝賽爾曲線路徑
 * @param  {Object} ctx
 * @param  {Array} p0
 * @param  {Array} p1
 * @param  {Array} p2
 */
function drawCurvePath( ctx, p0, p1, p2 ) {
    // ...
}

前文提到過(guò),在canvas中,繪制二次貝賽爾曲線的方法是quadraticCurveTo,所以只要短短兩行就能完成這個(gè)方法。

/**
 * 繪制二次貝賽爾曲線路徑
 * @param  {CanvasRenderingContext2D} ctx
 * @param  {Array} p0
 * @param  {Array} p1
 * @param  {Array} p2
 */
function drawCurvePath( ctx, p0, p1, p2 ) {
    ctx.moveTo( p0[ 0 ], p0[ 1 ] );
    ctx.quadraticCurveTo( 
        p1[ 0 ], p1[ 1 ],
        p2[ 0 ], p2[ 1 ]
    );
}

這樣就完成了基本的繪制二次貝塞爾曲線的方法了。

但是函數(shù)這樣設(shè)計(jì)有點(diǎn)小問(wèn)題

如果我們是在做一個(gè)圖形庫(kù),我們想給使用者提供一個(gè)繪制曲線的方法。

對(duì)于使用者來(lái)說(shuō),他只想在給定的起點(diǎn)和終點(diǎn)間間繪制一條曲線,他想要得到的曲線盡量美觀,但是又不想關(guān)心具體的實(shí)現(xiàn)細(xì)節(jié),如果還需要給第三個(gè)點(diǎn),使用者會(huì)有一定的學(xué)習(xí)成本(至少需要弄明白什么是貝塞爾曲線)。

看到這里你可能會(huì)比較疑惑,即使是二次貝塞爾曲線也需要三個(gè)控制點(diǎn),只有起點(diǎn)和終點(diǎn)怎么繪制曲線呢。

我們可以在起點(diǎn)和終點(diǎn)的垂直平分線上選一點(diǎn)作為第三個(gè)控制點(diǎn),可以提供給使用者一個(gè)參數(shù)來(lái)控制曲線的彎曲程度,現(xiàn)在函數(shù)就變成了這樣

/**
 * 繪制一條曲線路徑
 * @param  {CanvasRenderingContext2D} ctx
 * @param  {Array} start 起點(diǎn)
 * @param  {Array} end 終點(diǎn)
 * @param  {number} curveness 曲度(0-1)
 */
function drawCurvePath( ctx, start, end, curveness ) {
    // ...
}

我們用curveness來(lái)表示曲線的彎曲程度,也就是第三個(gè)控制點(diǎn)的偏離程度。這樣很容易就能計(jì)算出中間點(diǎn)。
現(xiàn)在完整的函數(shù)變成了這樣:

/**
 * 繪制一條曲線路徑
 * @param  {Object} ctx canvas渲染上下文
 * @param  {Array} start 起點(diǎn)
 * @param  {Array} end 終點(diǎn)
 * @param  {number} curveness 曲度(0-1)
 */
function drawCurvePath( ctx, start, end, curveness ) {
    // 計(jì)算中間控制點(diǎn)
    var cp = [
         ( start[ 0 ] + end[ 0 ] ) / 2 - ( start[ 1 ] - end[ 1 ] ) * curveness,
         ( start[ 1 ] + end[ 1 ] ) / 2 - ( end[ 0 ] - start[ 0 ] ) * curveness
    ];
    ctx.moveTo( start[ 0 ], start[ 1 ] );
    ctx.quadraticCurveTo( 
        cp[ 0 ], cp[ 1 ],
        end[ 0 ], end[ 1 ]
    );
}

對(duì),就這么短短幾行,接下來(lái)我們就可以通過(guò)它來(lái)繪制一條曲線了,代碼如下



    
        draw curve
    
    
        
        
    

繪制結(jié)果:


繪制一條曲線

繪制貝塞爾曲線動(dòng)畫(huà)

終于來(lái)到文章的本體啦,我們的目的不是繪制一條靜態(tài)的曲線,我們想繪制一條有過(guò)渡效果的曲線。

簡(jiǎn)化一下問(wèn)題,那就是我們希望繪制曲線的函數(shù)還接受另一個(gè)參數(shù),表示繪制曲線的百分比。我們定時(shí)去調(diào)用這個(gè)函數(shù),遞增百分比這個(gè)參數(shù),就能畫(huà)出動(dòng)畫(huà)了。

我們新增一個(gè)參數(shù)percent來(lái)表示百分比,現(xiàn)在函數(shù)變成了這樣:

/**
 * 繪制一條曲線路徑
 * @param  {Object} ctx canvas渲染上下文
 * @param  {Array} start 起點(diǎn)
 * @param  {Array} end 終點(diǎn)
 * @param  {number} curveness 曲度(0-1)
 * @param  {number} percent 繪制百分比(0-100)
 */
function drawCurvePath( ctx, start, end, curveness, percent ) {
    // ...
}

但是canvas提供的quadraticCurveTo方法只能繪制一條完整的二次貝賽爾曲線,沒(méi)有辦法去控制它只畫(huà)一部分。

畫(huà)完后用clearRect擦除掉一部分?這不太可行,因?yàn)楹茈y確定要擦除的范圍。如果曲線的線寬比較寬,就還需要保證擦除的邊界和曲線末端垂直,問(wèn)題就變得很復(fù)雜了。

現(xiàn)在再重新看看這張圖

我們是不是可以將percent這個(gè)參數(shù)理解成t值,然后通過(guò)貝賽爾曲線方程去計(jì)算出中間所有的點(diǎn),用直線連接起來(lái),以此模擬繪制貝賽爾曲線的一部分呢?

方法一

我們不再用canvas提供的quadraticCurveTo來(lái)繪制曲線,而是通過(guò)貝賽爾曲線的方程計(jì)算出一系列點(diǎn),用多端直線來(lái)模擬曲線。

這樣做的好處時(shí),我們可以很容易的控制繪制的范圍。

那么函數(shù)實(shí)現(xiàn)就變成了這樣:

/**
 * 繪制一條曲線路徑
 * @param  {Object} ctx canvas渲染上下文
 * @param  {Array} start 起點(diǎn)
 * @param  {Array} end 終點(diǎn)
 * @param  {number} curveness 曲度(0-1)
 * @param  {number} percent 繪制百分比(0-100)
 */
function drawCurvePath( ctx, start, end, curveness, percent ) {

    var cp = [
         ( start[ 0 ] + end[ 0 ] ) / 2 - ( start[ 1 ] - end[ 1 ] ) * curveness,
         ( start[ 1 ] + end[ 1 ] ) / 2 - ( end[ 0 ] - start[ 0 ] ) * curveness
    ];
    
    ctx.moveTo( start[ 0 ], start[ 1 ] );
    
    for ( var t = 0; t <= percent / 100; t += 0.01 ) {

        var x = quadraticBezier( start[ 0 ], cp[ 0 ], end[ 0 ], t );
        var y = quadraticBezier( start[ 1 ], cp[ 1 ], end[ 1 ], t );
        
        ctx.lineTo( x, y );
    }
    
}

function quadraticBezier( p0, p1, p2, t ) {
    var k = 1 - t;
    return k * k * p0 + 2 * ( 1 - t ) * t * p1 + t * t * p2;    // 這個(gè)方程就是二次貝賽爾曲線方程
}

接下來(lái)就可以通過(guò)設(shè)置定時(shí)器,每隔一段時(shí)間調(diào)用一次這個(gè)方法,并且遞增percent

為了動(dòng)畫(huà)更加平滑,我們使用requestAnimationFrame來(lái)代替定時(shí)器



    
        draw curve
    
    
        
        
    

得到的結(jié)果:

這樣基本實(shí)現(xiàn)了我們的需求,但它有一個(gè)問(wèn)題:

測(cè)試發(fā)現(xiàn),進(jìn)行一次lineTo的時(shí)間和一次quadraticCurveTo的時(shí)間差不多,但是quadraticCurveTo只需要一次就能畫(huà)出曲線,而使用lineTo則需要數(shù)十次。

換言之,用這樣的方式繪制曲線,和我們前面的實(shí)現(xiàn)方式相比性能下降了數(shù)十倍之多。在繪制一條曲線時(shí)可能感覺(jué)不到區(qū)別,但是如果需要同時(shí)繪制上千條曲線,性能就會(huì)受到很大的影響。

方法二

那有沒(méi)有什么方法可以做到用quadraticCurveTo來(lái)實(shí)現(xiàn)繪制完整曲線的一部分呢?

我們?cè)俅位氐竭@張圖

在中間的某一時(shí)刻,例如t=0.25時(shí),它是這樣的:

我們注意到,曲線P0-B這一段似乎也是貝賽爾曲線,它的控制點(diǎn)變成了P0,Q0,B。

現(xiàn)在問(wèn)題就迎刃而解了,我們只需要每次計(jì)算出Q0,B,就能得到其中一小段貝賽爾曲線的控制點(diǎn),然后就可以通過(guò)quadraticCurveTo來(lái)繪制它了。

代碼如下:

/**
 * 繪制一條曲線路徑
 * @param  {Object} ctx canvas渲染上下文
 * @param  {Array} start 起點(diǎn)
 * @param  {Array} end 終點(diǎn)
 * @param  {number} curveness 曲度(0-1)
 * @param  {number} percent 繪制百分比(0-100)
 */
function drawCurvePath( ctx, start, end, curveness, percent ) {

    var cp = [
         ( start[ 0 ] + end[ 0 ] ) / 2 - ( start[ 1 ] - end[ 1 ] ) * curveness,
         ( start[ 1 ] + end[ 1 ] ) / 2 - ( end[ 0 ] - start[ 0 ] ) * curveness
    ];
    
    var t = percent / 100;
    
    var p0 = start;
    var p1 = cp;
    var p2 = end;
    
    var v01 = [ p1[ 0 ] - p0[ 0 ], p1[ 1 ] - p0[ 1 ] ];     // 向量
    var v12 = [ p2[ 0 ] - p1[ 0 ], p2[ 1 ] - p1[ 1 ] ];     // 向量

    var q0 = [ p0[ 0 ] + v01[ 0 ] * t, p0[ 1 ] + v01[ 1 ] * t ];
    var q1 = [ p1[ 0 ] + v12[ 0 ] * t, p1[ 1 ] + v12[ 1 ] * t ];
    
    var v = [ q1[ 0 ] - q0[ 0 ], q1[ 1 ] - q0[ 1 ] ];       // 向量

    var b = [ q0[ 0 ] + v[ 0 ] * t, q0[ 1 ] + v[ 1 ] * t ];
    
    ctx.moveTo( p0[ 0 ], p0[ 1 ] );

    ctx.quadraticCurveTo( 
        q0[ 0 ], q0[ 1 ],
        b[ 0 ], b[ 1 ]
    );

}

將前面寫(xiě)的頁(yè)面替換成上面的代碼,可以看到得到的結(jié)果是一樣的:

繪制動(dòng)畫(huà)

現(xiàn)在已經(jīng)解決了最關(guān)鍵的問(wèn)題,我們可以繪制動(dòng)畫(huà)啦。
不過(guò)這一部分并不重要,我就不貼代碼了。

完整代碼可以看這里

結(jié)束

我的博客地址: https://github.com/hujiulong/...

我會(huì)在這里分享我的學(xué)習(xí)成果和經(jīng)驗(yàn),特別是canvas/WebGL/svg這方面的技術(shù)。如果有對(duì)前端圖形繪制感興趣的同學(xué)可以關(guān)注一下我的博客,收藏點(diǎn)star,訂閱點(diǎn)watch。

最近才將博客搬到github,所以文章并不多,我會(huì)堅(jiān)持寫(xiě)下去的!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90609.html

相關(guān)文章

  • 如何理解并應(yīng)塞爾曲線

    摘要:動(dòng)畫(huà)曲線的應(yīng)用了解了如何用貝塞爾曲線來(lái)指定動(dòng)畫(huà)曲線后,很多動(dòng)畫(huà)涉及到速度方面的效果就可以實(shí)現(xiàn)了,例如小車(chē)加速剎車(chē),彈簧動(dòng)畫(huà)等速度軌跡都可以根據(jù)自己的需要來(lái)進(jìn)行定制。 貝塞爾曲線又叫貝茲曲線,在大學(xué)高數(shù)中一度讓我非常頭疼。前陣子練手寫(xiě)動(dòng)畫(huà)的時(shí)候,發(fā)現(xiàn)貝塞爾曲線可以應(yīng)用于軌跡的繪制以及定義動(dòng)畫(huà)曲線。 本文就來(lái)探究一下,貝塞爾曲線到底是個(gè)什么樣的存在。 貝塞爾曲線原理 貝塞爾曲線由n個(gè)點(diǎn)來(lái)決...

    余學(xué)文 評(píng)論0 收藏0
  • 初識(shí)塞爾曲線

    摘要:貝塞爾曲線被廣泛用于制圖軟件中。多邊二次貝塞爾曲線可以看到有兩個(gè)控制點(diǎn),這樣連續(xù)畫(huà)出來(lái)了。如果前一個(gè)曲線不存在,當(dāng)前點(diǎn)就是第一個(gè)控制點(diǎn)在中使用繪制二次貝塞爾曲線,參數(shù)分別為控制點(diǎn)和終點(diǎn)的值。繪制三次貝塞爾曲線。 貝塞爾曲線在CSS動(dòng)畫(huà)中和canvas、svg繪圖中都是比較重要的一個(gè)東西!所以我來(lái)好好的小結(jié)一下關(guān)于它的一些東西。 什么是貝塞爾曲線 貝塞爾曲線于1962,由法國(guó)工程師皮埃爾...

    MasonEast 評(píng)論0 收藏0
  • 簡(jiǎn)單的canvas翻角效果

    摘要:由于工作需求需要寫(xiě)一個(gè)翻角效果鏈接右上角需要從無(wú)的狀態(tài)撕開(kāi)一個(gè)標(biāo)記且有動(dòng)畫(huà)過(guò)程上圖是實(shí)現(xiàn)的效果圖不是對(duì)這個(gè)翻角效果的難點(diǎn)在于沒(méi)有翻開(kāi)的時(shí)候露出的是下面的內(nèi)容實(shí)現(xiàn)角度來(lái)說(shuō)純動(dòng)畫(huà)的設(shè)計(jì)方案并沒(méi)有相出一個(gè)好的對(duì)策于是撿起了好久之前學(xué)的入門(mén)級(jí)別的下 由于工作需求 , 需要寫(xiě)一個(gè)翻角效果;showImg(https://segmentfault.com/img/bVYVm4?w=135&h=12...

    kun_jian 評(píng)論0 收藏0
  • 簡(jiǎn)單的canvas翻角效果

    摘要:由于工作需求需要寫(xiě)一個(gè)翻角效果鏈接右上角需要從無(wú)的狀態(tài)撕開(kāi)一個(gè)標(biāo)記且有動(dòng)畫(huà)過(guò)程上圖是實(shí)現(xiàn)的效果圖不是對(duì)這個(gè)翻角效果的難點(diǎn)在于沒(méi)有翻開(kāi)的時(shí)候露出的是下面的內(nèi)容實(shí)現(xiàn)角度來(lái)說(shuō)純動(dòng)畫(huà)的設(shè)計(jì)方案并沒(méi)有相出一個(gè)好的對(duì)策于是撿起了好久之前學(xué)的入門(mén)級(jí)別的下 由于工作需求 , 需要寫(xiě)一個(gè)翻角效果;showImg(https://segmentfault.com/img/bVYVm4?w=135&h=12...

    Magicer 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<