-
Notifications
You must be signed in to change notification settings - Fork 129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
用canvas绘制一个曲线动画——深入理解贝塞尔曲线 #1
Comments
支持。 |
我可以理解(start[ 0 ] + end[ 0 ] ) / 2是中点坐标,但是减去y轴差值乘以曲率( start[ 1 ] - end[ 1 ] ) * curveness理解不了!能否细说一下?谢谢 |
@a992681784 这个其实很容易理解 起点和终点分别为start,end var mid = [ ( start[ 0 ] + end[ 0 ] ) / 2, ( start[ 1 ] + end[ 1 ] ) / 2 ]; 根据起点和终点也可以得到一个向量 var v = [ end[ 0 ] - start[ 0 ], end[ 1 ] - start[ 1 ] ]; 将这个向量顺时针旋转90度,得到一个垂直于它的向量 var v2 = [ v[ 1 ], -v[ 0 ] ]; 那么中间控制点的坐标为(向量v2乘curveness加上中间点坐标) var cp = [
mid[ 0 ] + v2[ 0 ] * curveness,
mid[ 1 ] + v2[ 1 ] * curveness
] = [
( start[ 0 ] + end[ 0 ] ) / 2 - ( start[ 1 ] - end[ 1 ] ) * curveness,
( start[ 1 ] + end[ 1 ] ) / 2 - ( end[ 0 ] - start[ 0 ] ) * curveness
] |
感觉好深奥,看不懂 |
@gdmec07150725 唔,其实没什么深奥的,照着写一遍就熟悉了 |
@a992681784 这里得曲率不是圆周上半径R倒数得曲率,就是中垂线上得一个偏移比率而已 |
博主可以写个三次贝塞尔曲线的动画吗,根据两次的写不出来三次额 |
@zongyuan0204 你看“三次贝塞尔曲线绘制过程”这个动图,把那几个控制点的坐标算对,肯定能画出来的。你可以把控制点和它们的连线也画出来,找找问题出在哪 |
如果是多阶的贝塞尔曲线,且可动态增加控制点,有什么好办法吗? |
@navono 你的问题是什么? |
没事。已经解决了。 |
好想把数学老师请来坐在旁边~ |
笔芯 |
nice, 建议第一张图可以再清晰一丢丢 |
一处小笔误: |
二次贝塞尔曲线此处是否有误: |
最后还是用到了clearRect啊 |
如果要实现三次贝塞尔曲线绘制过程,是不是只能提供4个点作为参数,不能使用曲率了? 可以怎么实现? |
全部以向量来理解,就很清晰了 |
再重复一次这个步骤,通过Q1和Q2计算出B,使得length( Q0, Q1 ) = length( Q0, B ) * t。 这句写错了。是等号前面乘以t,不是后面 |
@hujiulong 大佬,我问个高深点的。如果你的曲线是三次贝塞尔,那么,如果只取曲线的一部分,还能用方程表达么? |
和二次贝塞尔曲线一样的,计算出对应的控制点位置即可 |
大佬,我实在想象不出三阶贝塞尔的局部怎么计算控制点,大佬能不能写一个DEMO给大家参考,毕竟搜索这方面需求,搜索引擎都会指向你这个文章 不是不相信你,我们大家想开开眼界 ^ ^ |
参考 |
前言
在前端开发中,贝赛尔曲线无处不在:
transition-timing-function
属性,可以使用贝塞尔曲线来描述过渡的缓动计算这篇文章我准备从实现一个非常简单的曲线动画效果入手,帮助大家彻底地弄懂什么是贝塞尔曲线,以及它有哪些特性,文章中有一点点数学公式,但是都非常简单:)。
实现这样一个曲线动画
可以点击这里查看在线演示
在写代码之前,先了解一下什么是贝塞尔曲线吧。
贝塞尔曲线
贝塞尔曲线(Bezier curve)是计算机图形学中相当重要的参数曲线,它通过一个方程来描述一条曲线,根据方程的最高阶数,又分为线性贝赛尔曲线,二次贝塞尔曲线、三次贝塞尔曲线和更高阶的贝塞尔曲线。
下面详细介绍一下用得比较多的二次贝塞尔曲线和三次贝塞尔曲线
二次贝塞尔曲线
二次贝塞尔曲线由三个点
P0
,P1
,P2
来确定,这些点也被称作控制点。曲线的方程为:这个方程其实有它的几何意义,它表示可以通过这样的步骤来绘制一条曲线:
0-1
的t
值P0
和P1
计算出点Q0
,Q0
在P0
P1
连成的直线上,并且length( P0, Q0 ) = length( P0, P1 ) * t
P1
和P2
计算出Q1
,使得length( P1, Q1 ) = length( P1, P2 ) * t
Q1
和Q2
计算出B
,使得length( Q0, Q1 ) = length( Q0, B ) * t
。B
就为当前曲线上的点注:上面的
length
表示两点之间的长度图:二次贝塞尔曲线结构
有了曲线方程,我们直接代入具体的
t
值就能算出点B
了。如果将
t
的值从0
过渡到1
,不断计算点B
,就可以得到一条二次贝塞尔曲线:图:二次贝塞尔线绘制过程
在canvas中,绘制二次贝塞尔曲线的方法为
其中
p1x, p1y, p2x, p2y
为后两个控制点(P1
和P2
)的横纵坐标,它默认将当前路径的起点作为一个控制点(P0
)。三次贝塞尔曲线
三次贝塞尔曲线需要四个点
P0
,P1
,P2
,P3
来确定,曲线方程为它的计算过程和二次贝塞尔曲线类似,这里不再赘述,可以看下图:
图:三次贝塞尔曲线结构
同样,将
t
的值从0
过渡到1
,就可以绘制出一条三次贝塞尔曲线:图:三次贝塞尔曲线绘制过程
在canvas中,绘制三次贝塞尔曲线的方法为
其中
p1x, p1y, p2x, p2y, p3x, p3y
为后三个控制点(P1
,P2
和P3
)的横纵坐标,它默认将当前路径的起点作为一个控制点(P0
)。贝塞尔曲线的特征
在三次贝塞尔曲线后面,还有更高阶的贝塞尔曲线,同样它们绘制的过程也更加复杂
四次贝塞尔曲线
图:四次贝塞尔曲线
五次贝塞尔曲线
图:五次贝塞尔曲线
我们可以归纳出贝塞尔曲线有几个重要的特征:
绘制贝塞尔曲线
复习完基础概念,接下来就要讲怎样绘制贝塞尔曲线啦
为简单起见,我们选择使用二次贝塞尔曲线。
我们先不考虑动画的事,我们先将问题简化成:给定一个起点和一个终点,需要实现一个函数,它能够绘制出一条曲线。
也就是说我们需要实现一个函数
drawCurvePath
,除渲染上下文ctx外(不清楚ctx是什么的同学可以先熟悉下canvas的基本概念),它接受三个参数,分别为二次贝塞尔曲线的三个控制点。我们将样式控制移到函数外,drawCurvePath
只用来绘制路径。前文提到过,在canvas中,绘制二次贝赛尔曲线的方法是
quadraticCurveTo
,所以只要短短两行就能完成这个方法。这样就完成了基本的绘制二次贝塞尔曲线的方法了。
但是函数这样设计有点小问题
如果我们是在做一个图形库,我们想给使用者提供一个绘制曲线的方法。
对于使用者来说,他只想在给定的起点和终点间间绘制一条曲线,他想要得到的曲线尽量美观,但是又不想关心具体的实现细节,如果还需要给第三个点,使用者会有一定的学习成本(至少需要弄明白什么是贝塞尔曲线)。
看到这里你可能会比较疑惑,即使是二次贝塞尔曲线也需要三个控制点,只有起点和终点怎么绘制曲线呢。
我们可以在起点和终点的垂直平分线上选一点作为第三个控制点,可以提供给使用者一个参数来控制曲线的弯曲程度,现在函数就变成了这样
我们用
curveness
来表示曲线的弯曲程度,也就是第三个控制点的偏离程度。这样很容易就能计算出中间点。现在完整的函数变成了这样:
对,就这么短短几行,接下来我们就可以通过它来绘制一条曲线了,代码如下
绘制结果:
绘制一条曲线
绘制贝塞尔曲线动画
终于来到文章的本体啦,我们的目的不是绘制一条静态的曲线,我们想绘制一条有过渡效果的曲线。
简化一下问题,那就是我们希望绘制曲线的函数还接受另一个参数,表示绘制曲线的百分比。我们定时去调用这个函数,递增百分比这个参数,就能画出动画了。
我们新增一个参数
percent
来表示百分比,现在函数变成了这样:但是canvas提供的
quadraticCurveTo
方法只能绘制一条完整的二次贝赛尔曲线,没有办法去控制它只画一部分。画完后用
clearRect
擦除掉一部分?这不太可行,因为很难确定要擦除的范围。如果曲线的线宽比较宽,就还需要保证擦除的边界和曲线末端垂直,问题就变得很复杂了。现在再重新看看这张图
我们是不是可以将
percent
这个参数理解成t
值,然后通过贝赛尔曲线方程去计算出中间所有的点,用直线连接起来,以此模拟绘制贝赛尔曲线的一部分呢?方法一
我们不再用canvas提供的
quadraticCurveTo
来绘制曲线,而是通过贝赛尔曲线的方程计算出一系列点,用多端直线来模拟曲线。这样做的好处时,我们可以很容易的控制绘制的范围。
那么函数实现就变成了这样:
接下来就可以通过设置定时器,每隔一段时间调用一次这个方法,并且递增percent
为了动画更加平滑,我们使用
requestAnimationFrame
来代替定时器得到的结果:
这样基本实现了我们的需求,但它有一个问题:
测试发现,进行一次
lineTo
的时间和一次quadraticCurveTo
的时间差不多,但是quadraticCurveTo
只需要一次就能画出曲线,而使用lineTo
则需要数十次。换言之,用这样的方式绘制曲线,和我们前面的实现方式相比性能下降了数十倍之多。在绘制一条曲线时可能感觉不到区别,但是如果需要同时绘制上千条曲线,性能就会受到很大的影响。
方法二
那有没有什么方法可以做到用
quadraticCurveTo
来实现绘制完整曲线的一部分呢?我们再次回到这张图
在中间的某一时刻,例如t=0.25时,它是这样的:
我们注意到,曲线
P0-B
这一段似乎也是贝赛尔曲线,它的控制点变成了P0,Q0,B
。现在问题就迎刃而解了,我们只需要每次计算出
Q0,B
,就能得到其中一小段贝赛尔曲线的控制点,然后就可以通过quadraticCurveTo
来绘制它了。代码如下:
将前面写的页面替换成上面的代码,可以看到得到的结果是一样的:
绘制动画
现在已经解决了最关键的问题,我们可以绘制动画啦。
不过这一部分并不重要,我就不贴代码了。
完整代码可以看这里
关于我的博客
这篇文章到这里就结束了。
我计划写一系列关于前端图形渲染的文章,将会涵盖常用的前端图形绘制技术:canvas、svg和WebGL。希望通过这一系列文章能让读者对前端的各种图形绘制接口以及图像处理、图形学的基础知识有所了解。希望在分享的同时,也能巩固和复习自己所学知识,和大家共同进步。
如果能帮助到你,欢迎star,这样也能及时追踪博客的更新。
The text was updated successfully, but these errors were encountered: