深度掌握SVG路径path的贝塞尔曲线指令

发布于 大漠

特别声明:本文转载@张鑫旭老湿在2014年06月份写的《深度掌握SVG路径path的贝塞尔曲线指令》,如需转载,烦请注明原文出处:http://www.zhangxinxu.com/wordpress/?p=4197

数字、公式、函数、变量,哦,NO!

又又一次说起贝塞尔曲线(英语:Bézier curve维基百科详尽中文释义戳这里),我最近在尝试实现复杂的矢量图形动画,发现对贝塞尔曲线的理解馒头那么厚,是完全不能承受富有创意的创作的,至少得有我当年追我老婆的脸皮厚才行。

然而,瞅瞅维基百科上的释义,或者其他一些相关的技术文章,总是离不开各种公式,一大堆变量……例如下面维基截图缩略图:

完全是数学爱好者的菜啊!我想,要是让UI设计师们去学习这些东西,估计还不如一刀来个痛快的!

这就是爱好领域与能力掌握的悖论!

从思维方式以及理论根基上来讲,设计师更能做出细腻、灵动的动画效果。在传统动画领域,往往都是美术人员做主导实现的,毕竟是需要手工一幅一幅绘制的(Flash动画也需要大量绘制)。

然而,在Web上,例如WebGL, Canvas或者SVG, 目前都还没有什么可视化的工具,可以让设计师直接摆弄摆弄就能出现自己想要的效果。唯一的解决之道就是——coding。

于是问题来了,设计师满满一颗做动画做效果的心,可惜码代码不是他们喜欢的菜;码农虽然喜欢码代码,可以让SVG等动起来,但是绘画能力有限,图形不敏感,动画把控不到位。我本将心照明月,奈何明月太高上不去!正因为这种无奈,所以才有了细化的职位分工。策划负责创意,设计师负责基础图形,脚本工程师浮动动起来。看上去很不错,然而,脚本工程师能否全方位体现最初创意与设想就要打一个大大的问号了?

凡是衔接(尤其是有血有肉有思考有创造力的个体)必定会出现接缝。我小时候玩过一个游戏,「悄悄话传递」,老师告诉第一个人的话可能是:“张含韵我喜欢你”,但是,传到最后一个人可能是,“张含韵傻逼啊你”!

因此,本职扎实,其他上下游衔接也有所涉猎的员工能做出更犀利的产品。

不知道大家有没有看过「QQ浏览器mac版」的宣传页,使用Chrome等高级浏览器,效果很赞,同行们都赞不绝口!

不少人调侃,要是浏览器做的有这个页面好那就好了!

哈,小道消息,此页面是从界面设计到效果实现完全是一个人完成的,一个懂CSS3的设计师做出来的产品。我懂技术,同时又是我自己的设计,我自然知道该怎么设计才能做出最理想的效果,自然知道怎样的技术可以实现我想要的设计。

然而,然而,注意,我要转折了……

「QQ浏览器mac版」的宣传页的效果虽好,但是,细细一查看,会发现,所有灿烂的效果元素,全部都是png24图片,然后CSS3 3D控制。

也就是数学的、逻辑的东西很少很少。在国外,HTML/CSS其实都是web设计师学习的必修课,很多优秀的页面重构er都是设计师背景。这种分工其实更符合做出偏前的优秀作品。

我经常会问求职者,你觉得你脱颖而出的地方是哪里?如果简历中只有显示做了很多网站,我觉得这远远不够!由于没有很多人都讨厌的,被高考拍死的数学逻辑,HTML/CSS的入门实际是很简单(但这种简单的意义非常深远,以后再聊),门槛很低,效果又是所见即所得,容易培养兴趣,所以茫茫中国,会写页面的人何其多。要想脱颖而出,要么有万人之上的深度,要么万人玩不来的技能

万人之上的深度:勤奋刻苦会让你有所成,但是,想要登峰造极那必须是要有天赋的。所以,单纯想通过HTML/CSS走上人生巅峰,如果没有足够的天赋,以及超幸运的环境支持(公司写代码的全男的,就我一个女的,还美女),那么你会很快撞到人生的天花板。

万人不会的技能:在Web领域,我觉得此技能为两块:一是扎实的设计功力,二是数学功底与逻辑思维能力。二者其一即可(兼得的跟我们不是一个档次的,不予讨论)。小时候的熏陶、绘画基本功练习,设计理论的学习,这些是很难被一般人超越的。所以,普通人想去转行做设计,除了一些罕见的具有天赋的人,往往会拿激情当才能,最后苦逼命!第二个,就是数学功底与逻辑思维能力。恰好,今天微博上看到了一张图:

虽然有玩笑成分,但不可厚非,工作之后的数学功力是个很明显的技能优势。恰好,动画,web矢量绘制需要用到大量的数学知识。于是,大家是不是看到了一个可以跟其他芸芸区分的方向?至于逻辑思维,更多涉及JavaScript以及后台开发等相关知识,即计算机背景优势,阿里的前端基本上都是这么一出。

于是,我提炼了下可以脱颖而出的技能组合:

所以,要想前端有所成,有两条路,一是往前,WebGL, Canvas, SVG领域,这需要对图形敏感,有设计感,有动画素养,有相当的数学知识,以及最重要的JavaScript控制能力;一是往后,走开发路线,工具,富应用,运维(数据统计、前端安全、前端部署)领域,这需要懂后台、计算机网络、逻辑思考能力,以及最重要的JavaScript开发功力。

无论那条路,JavaScript的掌握都是基础、都是中枢。So, 虽然新技术,新方向很诱人,但是,如果基础不好,走两步就会摔倒,吃力不讨好。静下心来,好好扎实前端基础再说,年轻当时,步子跨大了,当心扯着你未来老婆的最爱。

好了,方向是明确了,原因也知道了,该如何做呢?

要想进入前端图形领域,身为动画基础、路径绘制基础的贝塞尔曲线那必须要掌握透彻,烂熟于心啊!可是,可是……看到那比棒棒糖还长的数学公式、一堆还给老师的符号,我,我怕……

我其实也很怕,但是呢,贝塞尔曲线数学公式的意义在于,让你知道,贝塞尔曲线是如何绘制的,属于更底一层的学习。我们不是做SVG编辑器,不是做像Photoshop那样的工具,因此,我们的学习可以再往上一层,关心贝塞尔曲线指令,让浏览器去帮我们绘制此指令下的贝塞尔曲线。至于公式,可以在我们对贝塞尔曲线足够熟悉之后,再去深入,那就是另外的文章了。

贝塞尔曲线基本概要

“贝塞尔曲线”是什么?这个问题大可不必关心,只要知道,这厮可以绘制任何曲线,自然包括直线。

贝塞尔曲线不太好掌握的原因之一就是它的兄弟姐妹有点多:线性贝塞尔曲线二次方贝塞尔曲线三次方贝塞尔曲线四次方贝塞尔曲线五次方贝塞尔曲线、……。

维基百科上有很赞但不推荐细看的Gif动画:

线性贝塞尔曲线演示动画,t[0,1]区间

二次贝塞尔曲线演示动画,t[0,1]区间

三次贝塞尔曲线演示动画,t[0,1]区间

四次贝塞尔曲线演示动画,t[0,1]区间

五次贝塞尔曲线演示动画,t[0,1]区间

以上动图都是基于数学公式,目前阶段接触公式不利于我们的学习,因此,大家感受一下就可以了。

SVG贝塞尔曲线指令概要

Canvas, 以及CSS3动画函数也有贝塞尔曲线的,但他们的用法形式上与SVG是不同的(访问该地址可大致感受其异同)。

SVG中path的元素,也就是路径绘制,属性名称是d, 具体值是由专门的**“指令字母+坐标值”**实现的,例如下面这个简单代码示意:

<path d="M10 10L90 90" stroke="#000000" style="stroke-width: 5px;"></path>

标准的指令字母是10个,外加1个非标准的,这个可以参见我翻译构建的Snap.svg项目Paper.path()页面中的表格:

命令 名称 参数
M moveto 移动到 (x,y)+
Z closepath 关闭路径 (none)
L lineto 画线到 (x,y)+
H horizontal lineto 水平线到 x+
V vertical lineto 垂直线到 y+
C curveto 三次贝塞尔曲线到 (x1 y1 x2 y2 x y)+
S smooth curveto 光滑三次贝塞尔曲线到 (x2 y2 x y)+
Q quadratic Bézier curveto  二次贝塞尔曲线到 (x1 y1 x y)+
T smooth quadratic Bézier curveto  光滑二次贝塞尔曲线到 (x y)+
A elliptical arc  椭圆弧 (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
R Catmull-Rom curveto*  Catmull-Rom曲线 x1 y1 (x y)+

其中,Catmull-Rom曲线不是标准的SVG命令,我们这里不予以讨论!

如果指令字母是大写的,例如M, 则表示坐标位置是绝对位置;如果指令字母小写的,例如m, 则表示坐标位置是相对位置。

本文全部使用大写字母做演示和说明。

其中,有5个指定属于基本指令,你也可以理解为“好理解好上手好记忆”的指令,见下表:

指令字母(绝对坐标) 中文含义 参数示意 具体说明
M 移动到(moveTo) x,y 路径起始点坐标
Z 闭合路径(closepath)   将路径的开始和结束点用直线连接
L 直线(lineTo) x,y 当前节点到指定(x,y)节点,直线连接
H 水平直线 x 保持当前点的y坐标不变,x轴移动到x, 形成水平线
V 垂直直线 y 保持当前点的x坐标不变,y轴移动到y, 形成垂直线

除了这5个参数少、直来直往的指令,剩下的,除了弧形命令A(Arcs),就都是与贝塞尔曲线相关的命令了。

弧形命令A虽然曲线更规律,但其由于不确定性(指定椭圆长半径和短半径,以及上面的两个点的圆弧,可上半弧也可下半弧),因此,还需要额外的参数保证其弧线唯一,所以其比贝塞尔曲线看上去要复杂,不过大家不要怕,这里我们不跟他打交道。

剩下的4个指令就是绘制贝塞尔曲线相关的了。

他们分别是:C大仙,S大仙,Q大仙,T大仙。合称“厕所切图(CSQT)大仙”。其中,C大仙,S大仙是一伙的,一个组合,你知道叫神马组合吗?「厕所大仙组合!」哎呀,这位观众,你好生了得,前途无量,答得太对了,就是“厕所(CS)大仙组合”;Q大仙,T大仙是另外一伙的,你知道……「切图大仙组合!」哎呀,这位观众,看来天空才是你的极限,你已经学会抢答啦!没错,就是“切图(QT)大仙组合”。

那这“厕所大仙组合”和“切图大仙组合”是怎么组合的呢?各自的模样、身世又是如何的呢?且听下回分解……

“厕所大仙组合”和“切图大仙组合”

厕所大仙组合

“厕所大仙组合”是专门绘制三次贝塞尔曲线的。厕所,是个真实世界的物体,是三维的,因此,跟三次贝塞尔曲线的“三”正好对应。

那三次贝塞尔曲线的样子是?

喏,这里各种尺寸杯罩形状都是“三次贝塞尔曲线”:

这种曲线,只用到了“厕所组合”中的“厕”,也就是指令C

为方便演示具体参数,我就拿上图第一个A杯罩的放大举例:

上图,是Adobe Photoshop中使用钢笔工具时候的截图再加工。所谓钢笔工具抠图实际上就是一个一个贝塞尔曲线连接的结果,各类图形绘制软件类似工具本质上都是贝塞尔曲线。

我们可以看到,上面图片有4个点出现,曲线的两个端点,以及两个控制点,这就是典型的三次贝塞尔曲线。

杯罩曲线是我们实际存在的路径(描边就可见),而两个控制点是看不见的(虚的),只是用来确定曲线的弧度等。

一般而言,“三次贝塞尔曲线”的指令是:

C x1 y1, x2 y2, x y 

记住,两个控制点写在前面,后面是一个实点。跟“虚虚实实”这个词前三个字对应,就很好记忆了!如果使用相坐标置则是(后面不展示):

c dx1 dy1, dx2 dy2, dx dy

以上就是C大仙基本模样了!

「擦卡马尼亚!明明图片上有4个点啊,你这里的C指令怎么就只有3个参数啊?」

「哦,这个啊。其中有一个点是起始点,一般是使用指令Mx, y的,这样1+3就等于4啦~」

或者直接往下浏览-逗号区分纵横轴:

<svg id="svg" width="200" height="100">
    <desc>三次贝塞尔曲线</desc><defs></defs>
    <path d="M20,20 C90,40 130,40 180,20" stroke="#000000" fill="none" style="stroke-width: 2px;"></path>
    <text x="90" y="60">A杯罩</text>
</svg>

或者逗号用来区分每个点坐标(主流写法):

<svg id="svg" width="200" height="100">
    <desc>三次贝塞尔曲线</desc><defs></defs>
    <path d="M20 20 C90 40, 130 40, 180 20" stroke="#000000" fill="none" style="stroke-width: 2px;"></path>
    <text x="90" y="60">A杯罩</text>
</svg>

或者不需要逗号:

<svg id="svg" width="200" height="100">
    <desc>三次贝塞尔曲线</desc><defs></defs>
    <path d="M20 20 C90 40 130 40 180 20" stroke="#000000" fill="none" style="stroke-width: 2px;"></path>
    <text x="90" y="60">A杯罩</text>
</svg>

效果(IE9+, …):

三次贝塞尔曲线 A杯罩

S大仙呢?

S大仙和C大仙是一对组合,职责之一就给C大仙补刀用的。

MDN上的这张图的示意很赞:

大家要把关注点放在蓝线上面。S指令会自动补出一个对称的控制点(蓝线部分)。于是,就会有连续的平滑曲线啦!

C指令有三个坐标参数,而S指令自动对称一个控制点,因此,跟在C指令之后的S指令,只需要2个参数哦,如下:

S x2 y2, x y

C指令+S指令,就是“厕所大仙组合”啦!

但是呢,只要是组合,就会出现矛盾,比如说科比和奥尼尔,连白娘子和许仙都有不信任的时候。所以,S大仙前面的搭档可能不是C大仙,而是S大仙,或者更苦逼的孤苦伶仃一个人。

S大仙生来补刀,无私,又简化参数,很高大上,但同时又是个很有个性的人。

如果前面跟着的是其他S大仙,上阵不离兄弟兵,那就给前面的S大仙补刀;但是,如果前面即没有C大仙,也没有S大仙,孤苦伶仃一个人被抛弃,则S大仙就会叛变,自降一级,从三次贝塞尔到二次贝塞尔组合阵营,也就是“切图大仙组合”,其表现就跟二次贝塞尔曲线的Q大仙一模一样(两个控制点合为同一个点),仿佛在宣告:「让你们无视我,我去跟Q大仙混!」

所以,亲们,不要让S大仙孤苦伶仃一个人哈~其不能一个人战斗,一个人战斗时候就变成其他阵营的了。

相关HTML代码如下:

<svg id="svg" width="200" height="100">
    <desc>三次贝塞尔平滑曲线</desc><defs></defs>
    <path d="M20 20 C90 40 130 40 180 20 S250 60 280 20" stroke="#000000" fill="none" style="stroke-width: 2px;"></path>
    <text x="90" y="60">A杯罩</text>
    <text x="230" y="60">赘肉小肚子</text>
</svg>

现代浏览器下效果如下:

三次贝塞尔平滑曲线 A杯罩 赘肉小肚子

切图大仙组合

好巧啊,刚刚提到了“切图(QT)大仙组合”中的“切(Q)大仙”。大家都知道,“切图”是个二维平面的活,因此,“切图大仙组合”是绘制二次贝塞尔曲线的。正好区别于:“厕所”是三维的,对应的是三次贝塞尔曲线。

QT大仙组合中的Q大仙对应CS组合中的C大仙,为二次贝塞尔曲线的主攻手。

二次比三次少一次,自然就要更简单点。

典型的Q大仙对应的二次贝塞尔曲线如下图:

对应的指令和参数是:

Q x1 y1, x y

可以看到,跟三次贝塞尔曲线相比,就是2个控制点合为1个,而孤零零的S大仙指令由于缺乏对称控制点,只能2个控制点合为1个,于是看上去就是叛逃到了Q大仙这里。

下面说说T大仙,T大仙与S大仙是一路人,专门做平滑补刀,看MDN上示意图,蓝色标注是关键:

自动补全对称的控制点(上图蓝色部分),让曲线平滑起来。与S大仙类似,前面需要是他的黄金搭档Q大仙,或者是兄弟搭档T大仙。如果你让T大仙孤苦伶仃一个人,性格桀骜的T大仙就会叛逃,与S大仙类似,逃到下一级阵营,二次贝塞尔曲线的下一级,让我想想,哦~~~好像是一次贝塞尔曲线,也就是直线啦!『没有花香,没有树高,我是一个无人关心的小草』此时,只有这首歌才能唱出孤苦伶仃T大仙的悲哀了!

之所以T大仙会变成直线,是因为T单独使用的时候,其控制点就会被认为和终点是同一个点,所以画出来的是一条直线。

无逗号版HTML代码示意:

<svg id="svg" width="300" height="100">
    <desc>二次贝塞尔平滑曲线</desc><defs></defs>
    <path d="M20 10 Q140 40 180 20 T280 30" stroke="#000000" fill="none" style="stroke-width: 2px;"></path>
    <text x="120" y="60">小蛮腰</text>
    <text x="200" y="40">小翘臀</text>
</svg>

支持SVG浏览器下效果:

二次贝塞尔平滑曲线 小蛮腰 小翘臀

四次贝塞尔曲线、五次……

万变不离其宗,对于四次贝塞尔曲线、五次贝塞尔曲线、……,我们使用二次贝塞尔与三次贝塞尔曲线的完美组合就OK了,不必细述。

补充于2015-01-05: DAYU做了个任意二次、三次贝塞尔曲线呈现工具,我觉得挺实用的,这里分享下

通晓SVG路径贝塞尔曲线指令的意义是?

这是个很好的问题,我们为何需要熟练掌握SVG路径贝塞尔曲线指令,是为了绘图?

我们有Adobe Illustrator, 有Google的SVG编辑器,我们可以直接所见即所得直接使用工具绘制,岂不比代码来得速度?

言之凿凿,实际上,我们掌握SVG贝塞尔曲线指令,绘图是小,控制绘图为大!

我们先看下面这个可爱的Gif跑步动画:

此图来自我厂微信团队白树的移动Web动画设计的一点心得——CSS3实现跑步一文,矢量风格的图片,可爱的Gif, 身为职业病的我们一定会窥视下是如何实现的,喜欢这类Gif的会求教程。

到底是怎么实现的呢?我不清楚原作者是怎么实现的,不过白树的实现方式一目了然,多个图片帧+CSS3 动画控制background-position实现的(下面两张为截图)。

请允许我“呵呵”一下,上面的实现方法属于设计师思维模式方法(避免数学以及JavaScript),有几个大问题:

  • 成本:绘图是个很非人力的活,连白树都感叹:用PS打开该大师的 GIF 图,在时间轴窗口中有 24 张不同的图片,通过一帧一帧的播放来实现跑步动画,很简单得说明做一个精细的动画需要多费点心思和劳动力啊,向大师表示敬礼~
  • 文件尺寸:为了压缩Sprite大小,白树这里只取了7张,但仍有37k, 37K啊,可以抵多少JS代码啊,而且还不能Gzip.
  • 动画效果:7帧的动画,呵呵,显然,很难细腻到哪里去,尤其速度慢的时候,使用代码控制,可以精确到小数点后N位的像素值。
  • 可控性:我想,借助CSS3动画,能控制的无非就是速度了。我想小妹妹上午红衣服,下午蓝衣服,估计只能另外搞一套图了。

如果你是资深设计师,同时通晓上述方法,赞!好厉害的设计师。如果你是重构,通晓上面的方法,鄙人总觉得只是职业钓鱼者又钓了一条鱼,还不足以让公司为你加薪。但是,如果你是重构,使用前端开发的思维方式去处理,通过Coding解决问题,啧啧,不得了,秒杀茫茫多重构啊!归结为:偏前的前端使用偏后的前端的技术解决偏前的问题获得更多的钱,而非偏前的前端使用偏前的技术解决偏前的问题!

例如这里,有没有想过使用SVG绘制此图,特定时间改变特定贝塞尔曲线值,重绘形成动画效果。好处不必多说:兼容Retina, 文件尺寸小的一塌糊涂,IE9浏览器也兼容(CSS3 animation只能IE10+),而且动画非常细腻,可控性非常高!只能用一个网络词形容——鸟炸天!

基本原理是,这种矢量风格的图形,就是由一些贝赛尔曲线绘制的路径填充色组合而来,例如那个细细的小腿,两个三次贝塞尔曲线(仅位置差别)+一个二次贝塞尔曲线(足底)就可以完成。我们要控制腿动,只要更改特定时间,贝塞尔曲线参数指就可以了。一般,这种有规律的运动是由数学函数的,非常好控制。就算运动无规律,找几个关键点做位移,也比一帧一帧绘制高效好多倍!然后,让SVG重新绘制,哇哦~动画就出现了。由于是SVG,我们可以很随意控制颜色,脑袋的位置,甚至可以与用户互动。例如,用户鼠标在哪里小妹妹就往哪个方向跑。

相比之下,图片帧+CSS3动画实现是不是弱爆了!偏开发的前端往往缺少设计的灵性、缺了点感性思维,虽然有做动画的程序功力,但,天资决定了动画的效果往往生硬;设计师虽然天生动画好手,也很感兴趣,然数学、脚本是个很深的鸿沟。此时,必须要游走于设计与开发的纯正前端出手了,这是我们的挑战,也是我们的机遇,不做出点东西来,单靠嘴皮子是无法提高话语权的。

很多人肯定想看我所说的SVG+JS控制曲线的动画效果。正在进行中……回头,一定会展示一些SVG驱动的可爱动画效果的

结束语

图形动画领域很多东西都已一脉相承,一通百通的。掌握SVG的贝塞尔曲线,肯定对于CSS3动画,Canvas的处理有很大帮助。动画算法也都是一致,图像处理算法也都是一致的。

好就这些,开篇已经吐槽够多了,欢迎纠正文中表述不准确的地方,欢迎沟通,欢迎交流。Nike Zoom All Out Flyknit