目錄
貝塞爾曲線
二次貝塞爾曲線
三次貝塞爾曲線
四次贝塞尔曲线
五次贝塞尔曲线
绘制贝塞尔曲线
绘制贝塞尔曲线动画
方法一
方法二
绘制动画
首頁 web前端 js教程 用canvas繪製一個曲線動畫實例

用canvas繪製一個曲線動畫實例

Jan 03, 2018 pm 03:38 PM
canvas 動畫

在前端開發中,貝賽爾曲線無所不在,本文主要和大家分享用canvas繪製一個曲線動畫實例,希望能幫助大家。
  • 它可以用來繪製曲線,在svg和canvas中,原生提供的曲線繪製都是使用貝賽爾曲線

  • 它也可以用來描述一個緩動演算法,設定css的transition-timing-function屬性,可以使用貝塞爾曲線來描述過渡的緩動計算

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

這篇文章我準備從實現一個非常簡單的曲線動畫效果入手,幫助大家徹底地弄清楚什麼是貝塞爾曲線,以及它有哪些特性,文章中有一點點數學公式,但是都非常簡單:)。

用canvas繪製一個曲線動畫實例實作這樣一個曲線動畫

可以點擊這裡查看線上示範

在寫程式碼之前,先了解什麼是貝塞爾曲線吧。

貝塞爾曲線

貝塞爾曲線(Bezier curve)是電腦圖形學中相當重要的參數曲線,它透過一個方程式來描述一條曲線,根據方程式的最高階數,又分為線性貝賽爾曲線,二次貝塞爾曲線、三次貝塞爾曲線和更高階的貝塞爾曲線。

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

二次貝塞爾曲線

二次貝塞爾曲線由三個點P0,P1,P2來決定,這些點也被稱為控制點。曲線的方程式為:

用canvas繪製一個曲線動畫實例

這個方程式其實有它的幾何意義,它表示可以透過這樣​​的步驟來畫出一條曲線:

  • 選定一個0-1t

  • 透過P0P1計算出點Q0Q0P0 P1連成的直線上,並且length( P0, Q0 ) = length( P0, P1 ) * t

  • #同樣,透過P1P2計算出Q1,使得length( P1, Q1 ) = length( P1, P2 ) * t

  • 再重複這個步驟,透過Q1Q2計算出B,使得length( Q0, Q1 ) = length( Q0, B ) * tB就為目前曲線上的點

註:上面的length表示兩點之間的長度

用canvas繪製一個曲線動畫實例
圖:二次貝塞爾曲線結構

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

如果將t的值從0轉換到1,不斷計算點B,就可以得到一條二次貝塞爾曲線:

用canvas繪製一個曲線動畫實例
圖:二次貝塞爾線繪製過程

在canvas中,繪製第二個貝塞爾曲線的方法為

ctx.quadraticCurveTo( p1x, p1y, p2x, p2y )
登入後複製

其中p1x, p1y, p2x, p2y為後兩個控制點(P1P2)的橫縱座標,它預設將目前路徑的起點作為一個控制點(P0)。

三次貝塞爾曲線

三次貝塞爾曲線需要四個點P0,P1,P2, P3來決定,曲線方程式為

用canvas繪製一個曲線動畫實例

#它的計算過程和二次貝塞爾曲線類似,這裡不再贅述,可以看下圖:

用canvas繪製一個曲線動畫實例
圖:三次貝塞爾曲線結構

同樣,將t的數值從0轉換到1,就可以畫出一條三次貝塞爾曲線:

用canvas繪製一個曲線動畫實例##圖:三次貝塞爾曲線繪製過程

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

ctx.bezierCurveTo( p1x, p1y, p2x, p2y, p3x, p3y )
登入後複製
其中

p1x, p1y, p2x, p2y, p3x, p3y為後三個控制點(P1,P2P3)的橫縱座標,它預設將目前路徑的起點作為一個控制點(P0)。

貝塞爾曲線的特徵

在三次貝塞爾曲線後面,還有更高階的貝塞爾曲線,同樣它們繪製的過程也更加複雜

四次贝塞尔曲线

用canvas繪製一個曲線動畫實例
图:四次贝塞尔曲线

五次贝塞尔曲线

用canvas繪製一個曲線動畫實例图:五次贝塞尔曲线

我们可以归纳出贝塞尔曲线有几个重要的特征:

  1. n阶贝塞尔曲线需要n+1个点来确定

  2. 贝塞尔曲线是平滑的

  3. 贝塞尔曲线的起点和终点与对应控制点的连线相切

绘制贝塞尔曲线

复习完基础概念,接下来就要讲如果绘制贝塞尔曲线啦

为简单起见,我们选择使用二次贝塞尔曲线

我们先不考虑动画的事,我们先将问题简化成:给定一个起点和一个终点,需要实现一个函数,它能够绘制出一条曲线。

也就是说我们需要实现一个函数drawCurvePath,除渲染上下文ctx外(不清楚ctx是什么的同学可以先熟悉下canvas的基本概念),它接受三个参数,分别为二次贝塞尔曲线的三个控制点。我们将样式控制移到函数外,drawCurvePath只用来绘制路径。

/**
 * 绘制二次贝赛尔曲线路径
 * @param  {Object} ctx
 * @param  {Array<number>} p0
 * @param  {Array<number>} p1
 * @param  {Array<number>} p2
 */
function drawCurvePath( ctx, p0, p1, p2 ) {
    // ...
}
登入後複製

前文提到过,在canvas中,绘制二次贝赛尔曲线的方法是quadraticCurveTo,所以只要短短两行就能完成这个方法。

/**
 * 绘制二次贝赛尔曲线路径
 * @param  {CanvasRenderingContext2D} ctx
 * @param  {Array<number>} p0
 * @param  {Array<number>} p1
 * @param  {Array<number>} p2
 */
function drawCurvePath( ctx, p0, p1, p2 ) {
    ctx.moveTo( p0[ 0 ], p0[ 1 ] );
    ctx.quadraticCurveTo( 
        p1[ 0 ], p1[ 1 ],
        p2[ 0 ], p2[ 1 ]
    );
}
登入後複製

这样就完成了基本的绘制二次贝塞尔曲线的方法了。

但是函数这样设计有点小问题

如果我们是在做一个图形库,我们想给使用者提供一个绘制曲线的方法。

对于使用者来说,他只想在给定的起点和终点间间绘制一条曲线,他想要得到的曲线尽量美观,但是又不想关心具体的实现细节,如果还需要给第三个点,使用者会有一定的学习成本(至少需要弄明白什么是贝塞尔曲线)。

看到这里你可能会比较疑惑,即使是二次贝塞尔曲线也需要三个控制点,只有起点和终点怎么绘制曲线呢。

我们可以在起点和终点的垂直平分线上选一点作为第三个控制点,可以提供给使用者一个参数来控制曲线的弯曲程度,现在函数就变成了这样

/**
 * 绘制一条曲线路径
 * @param  {CanvasRenderingContext2D} ctx
 * @param  {Array<number>} start 起点
 * @param  {Array<number>} end 终点
 * @param  {number} curveness 曲度(0-1)
 */
function drawCurvePath( ctx, start, end, curveness ) {
    // ...
}
登入後複製

我们用curveness来表示曲线的弯曲程度,也就是第三个控制点的偏离程度。这样很容易就能计算出中间点。
现在完整的函数变成了这样:

/**
 * 绘制一条曲线路径
 * @param  {Object} ctx canvas渲染上下文
 * @param  {Array<number>} start 起点
 * @param  {Array<number>} end 终点
 * @param  {number} curveness 曲度(0-1)
 */
function drawCurvePath( ctx, start, end, curveness ) {
    // 计算中间控制点
    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 ]
    );
}
登入後複製

对,就这么短短几行,接下来我们就可以通过它来绘制一条曲线了,代码如下

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>draw curve</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
        <script>
            var canvas = document.getElementById( 'canvas' );
            var ctx = canvas.getContext( '2d' );
            
            ctx.lineWidth = 2;
            ctx.strokeStyle = '#000';
            ctx.beginPath();
    
            drawCurvePath( 
                ctx,
                [ 100, 100 ],
                [ 200, 300 ],
                0.4
            );
            
            ctx.stroke();
            
            function drawCurvePath( ctx, start, end, curveness ) {
                // ...
            }
        </script>
    </body>
</html>
登入後複製

绘制结果:

用canvas繪製一個曲線動畫實例
绘制一条曲线

绘制贝塞尔曲线动画

终于来到文章的本体啦,我们的目的不是绘制一条静态的曲线,我们想绘制一条有过渡效果的曲线。

简化一下问题,那就是我们希望绘制曲线的函数还接受另一个参数,表示绘制曲线的百分比。我们定时去调用这个函数,递增百分比这个参数,就能画出动画了。

我们新增一个参数percent来表示百分比,现在函数变成了这样:

/**
 * 绘制一条曲线路径
 * @param  {Object} ctx canvas渲染上下文
 * @param  {Array<number>} start 起点
 * @param  {Array<number>} end 终点
 * @param  {number} curveness 曲度(0-1)
 * @param  {number} percent 绘制百分比(0-100)
 */
function drawCurvePath( ctx, start, end, curveness, percent ) {
    // ...
}
登入後複製

但是canvas提供的quadraticCurveTo方法只能绘制一条完整的二次贝赛尔曲线,没有办法去控制它只画一部分。

画完后用clearRect擦除掉一部分?这不太可行,因为很难确定要擦除的范围。如果曲线的线宽比较宽,就还需要保证擦除的边界和曲线末端垂直,问题就变得很复杂了。

现在再重新看看这张图

用canvas繪製一個曲線動畫實例

我们是不是可以将percent这个参数理解成t值,然后通过贝赛尔曲线方程去计算出中间所有的点,用直线连接起来,以此模拟绘制贝赛尔曲线的一部分呢?

方法一

我们不再用canvas提供的quadraticCurveTo来绘制曲线,而是通过贝赛尔曲线的方程计算出一系列点,用多端直线来模拟曲线。

这样做的好处时,我们可以很容易的控制绘制的范围。

那么函数实现就变成了这样:

/**
 * 绘制一条曲线路径
 * @param  {Object} ctx canvas渲染上下文
 * @param  {Array<number>} start 起点
 * @param  {Array<number>} end 终点
 * @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;    // 这个方程就是二次贝赛尔曲线方程
}
登入後複製

接下来就可以通过设置定时器,每隔一段时间调用一次这个方法,并且递增percent

为了动画更加平滑,我们使用requestAnimationFrame来代替定时器

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>draw curve</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
        <script>
            var canvas = document.getElementById( 'canvas' );
            var ctx = canvas.getContext( '2d' );
            
            ctx.lineWidth = 2;
            ctx.strokeStyle = '#000';
            
            var percent = 0;
            
            function animate() {
                
                ctx.clearRect( 0, 0, 800, 800 );
                ctx.beginPath();

                drawCurvePath( 
                    ctx,
                    [ 100, 100 ],
                    [ 200, 300 ],
                    0.2,
                    percent
                );
    
                ctx.stroke();
    
                percent = ( percent + 1 ) % 100;
                
                requestAnimationFrame( animate );
                
            }
            
            animate();
            
            function drawCurvePath( ctx, start, end, curveness, percent ) {
                // ...
            }
        </script>
    </body>
</html>
登入後複製

得到的结果:

用canvas繪製一個曲線動畫實例

这样基本实现了我们的需求,但它有一个问题:

测试发现,进行一次lineTo的时间和一次quadraticCurveTo的时间差不多,但是quadraticCurveTo只需要一次就能画出曲线,而使用lineTo则需要数十次。

换言之,用这样的方式绘制曲线,和我们前面的实现方式相比性能下降了数十倍之多。在绘制一条曲线时可能感觉不到区别,但是如果需要同时绘制上千条曲线,性能就会受到很大的影响。

方法二

那有没有什么方法可以做到用quadraticCurveTo来实现绘制完整曲线的一部分呢?

我们再次回到这张图

用canvas繪製一個曲線動畫實例

在中间的某一时刻,例如t=0.25时,它是这样的:

用canvas繪製一個曲線動畫實例

我们注意到,曲线P0-B这一段似乎也是贝赛尔曲线,它的控制点变成了P0,Q0,B

现在问题就迎刃而解了,我们只需要每次计算出Q0,B,就能得到其中一小段贝赛尔曲线的控制点,然后就可以通过quadraticCurveTo来绘制它了。

代码如下:

/**
 * 绘制一条曲线路径
 * @param  {Object} ctx canvas渲染上下文
 * @param  {Array<number>} start 起点
 * @param  {Array<number>} end 终点
 * @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 ] ];     // 向量<p0, p1>
    var v12 = [ p2[ 0 ] - p1[ 0 ], p2[ 1 ] - p1[ 1 ] ];     // 向量<p1, p2>

    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 ] ];       // 向量<q0, q1>

    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 ]
    );

}
登入後複製

将前面写的页面替换成上面的代码,可以看到得到的结果是一样的:

用canvas繪製一個曲線動畫實例

绘制动画

现在已经解决了最关键的问题,我们可以绘制动画啦。
不过这一部分并不重要,我就不贴代码了。

完整代码可以看这里

用canvas繪製一個曲線動畫實例

相关推荐:

Python之正弦曲线实现方法分析

使用CSS做贝塞尔曲线

css动画之模拟正余弦曲线的实例分享


以上是用canvas繪製一個曲線動畫實例的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

動畫不工作在PowerPoint中[修復] 動畫不工作在PowerPoint中[修復] Feb 19, 2024 am 11:12 AM

您是否正在嘗試製作演示文稿,但無法添加動畫?如果動畫在你的WindowsPC上的PowerPoint中不起作用,那麼這篇文章將會幫助你。這是一個常見的問題,許多人都在抱怨。例如,在Microsoft團隊中演示或在螢幕錄製期間,動畫可能會停止運作。在本指南中,我們將探索各種故障排除技術,以協助您修復在Windows上的PowerPoint中無法運作的動畫。為什麼我的PowerPoint動畫不起作用?我們注意到Windows上PowerPoint中的動畫可能無法運作問題的一些可能原因如下:由於個

CSS動畫:如何實現元素的閃光效果 CSS動畫:如何實現元素的閃光效果 Nov 21, 2023 am 10:56 AM

CSS動畫:如何實現元素的閃光效果,需要具體程式碼範例在網頁設計中,動畫效果有時可以為頁面帶來很好的使用者體驗。而閃光效果是一種常見的動畫效果,它可以使元素更加引人注目。以下將介紹如何使用CSS實現元素的閃光效果。一、閃光的基本實作首先,我們需要使用CSS的animation屬性來實現閃光效果。 animation屬性的值需要指定動畫名稱、動畫執行時間、動畫延遲時

ppt動畫如何設定先進入再退出 ppt動畫如何設定先進入再退出 Mar 20, 2024 am 09:30 AM

我們在日常的辦公中常常會使用到ppt,那麼你是否對ppt裡邊的每個操作功能都很了解呢?例如:ppt中怎麼設定動畫效果、怎麼設定切換效果、每個動畫的效果長度是多少?每個投影片能不能自動播放、ppt動畫先進入再退出等等,那麼今天這期我就先跟大家分享ppt動畫先進入再退出的具體操作步驟,就在下方,小伙伴們快來看一看吧! 1.首先,我們在電腦中開啟ppt,點選文字方塊外側選取文字框,(如下圖紅色圈出部分所示)。 2.然後,點選選單列中的【動畫】,選取【擦除】的效果,(如圖紅色圈出部分所示)。 3.接下來,點擊【

跳票 2 年,國產 3D 動畫電影《二郎神之深海蛟龍》定檔 7 月 13 日 跳票 2 年,國產 3D 動畫電影《二郎神之深海蛟龍》定檔 7 月 13 日 Jan 26, 2024 am 09:42 AM

本站1月26日消息,國產3D動畫電影《二郎神之深海蛟龍》發布一組最新劇照,正式宣布將於7月13日上映。據了解,《二郎神之深海蛟龍》是由迷狐星(北京)動漫有限公司、霍爾果斯眾合千澄影業有限公司、浙江橫店影業有限公司、浙江共贏影業有限公司、成都天火科技有限公司、華文映像(北京)影業有限公司出品,王君執導的動畫電影,原定2022年7月22日在中國大陸上映。本站劇情簡介:封神之戰後,姜子牙攜「封神榜」分封諸神,而後封神榜被天庭封印於九州祕境深海之下。事實上,除了分封神位,封神榜中還封緘著眾多強大的妖邪元

Netflix 黏土動畫電影《小雞快跑 2》終極預告公佈,12 月 15 日上線 Netflix 黏土動畫電影《小雞快跑 2》終極預告公佈,12 月 15 日上線 Nov 20, 2023 pm 01:21 PM

Netflix的黏土動畫電影《小雞快跑2》的最終預告片已經公佈,該影片預計將於12月15日上線本站注意到,《小雞快跑2》預告片展示了小雞洛基和金傑為了尋找女兒莫莉開展行動。莫莉被FunLand農場的一輛卡車帶走,洛基和金傑冒著危險找回女兒。該片由山姆・菲爾執導,並由桑迪韋・牛頓、扎克瑞・萊維、貝拉・拉姆齊、伊梅爾達・斯湯頓和大衛・布拉德利主演。據了解,《小雞快跑2》是繼《小雞快跑》之後時隔20多年推出的續集。第一部作品於2001年1月2日在中國上映,講述了一群小雞們在養雞廠面臨被做成雞肉餡餅的命運

宮崎駿動畫電影《紅豬》延長上映至明年 1 月 16 日,豆瓣 8.6 分 宮崎駿動畫電影《紅豬》延長上映至明年 1 月 16 日,豆瓣 8.6 分 Dec 18, 2023 am 08:07 AM

本站消息,宮崎駿動畫電影《紅豬》宣布將上映時間延長至2024年1月16日本站先前報道,《紅豬》已於11月17日登陸全國藝聯專線影院,累計票房超2000萬,豆瓣評分8.6分,4、5星好評佔85.8%。 《紅豬》由吉卜力工作室製作,宮崎駿執導,森山週一郎、加藤登紀子、大塚明夫、岡村明美等參與配音,最初於1992年在日本上映。該片改編自宮崎駿漫畫作品《飛行艇時代》,講述了義大利空軍的王牌飛行員波魯克・羅森被施了魔法變成了一頭豬。之後,他成為了一位賞金獵人,打擊空中劫匪,保護身邊人。劇情簡介:羅森是一戰中

Netflix 動畫影集《索尼克:回家大冒險》第三季片段公佈,明年上線 Netflix 動畫影集《索尼克:回家大冒險》第三季片段公佈,明年上線 Nov 12, 2023 am 09:25 AM

Netflix抱歉,我可以幫您重寫內容,但我需要知道您想要重寫的原始內容。可以提供給我嗎?在極客週上公佈了動畫影集《索尼克:回家大冒險》第三季片段,預計將於2024年上線抱歉,我可以幫您重寫內容,但我需要知道您想要重寫的原始內容。可以提供給我嗎?據本站了解,《索尼克:回家大冒險》由世嘉、WildBrain抱歉,我可以幫您重寫內容,但我需要知道您想要重寫的原始內容。可以提供給我嗎?工作室很抱歉,我可以幫您重寫內容,但我需要知道您想要重寫的原始內容。可以提供給我嗎?和抱歉,我可以幫您重寫內容,但我需要

學習canvas框架 詳解常用的canvas框架 學習canvas框架 詳解常用的canvas框架 Jan 17, 2024 am 11:03 AM

探索Canvas框架:了解常用的Canvas框架有哪些,需要具體程式碼範例引言:Canvas是HTML5中提供的一個繪圖API,透過它我們可以實現豐富的圖形和動畫效果。為了提高繪圖的效率和便利性,許多開發者開發了不同的Canvas框架。本文將介紹一些常用的Canvas框架,並提供具體程式碼範例,以幫助讀者更深入地了解這些框架的使用方法。一、EaselJS框架Ea

See all articles