12.13 HTML Canvas复杂路径
画布中的路径不仅限于线条和圆,实际上还可以用它们来创建各种奇妙的图形。
单独一条直线路径可以绘制成一条漂亮的线条,下面介绍如何将多个路径连接在一起。
context.beginPath(); context.moveTo(100, 50); context.lineTo(150, 150); context.lineTo(50, 150); context.closePath(); context.stroke(); context.fill();
你应该能够读懂所有这些代码—一它先开始一条路径,将原点移到当前路径,从当前路径原点绘制一条线到一个指定点,再绘制一条线到另一个点,然后再继续,那么我们在这里做了什么呢?我们刚刚做的就是将多个路径连接在一起,我们只需要不停地画线。这确实非常简单,每次调用moveTo或lineTo都会给所谓子路径(subpath)增加一个相应的(x,y)坐标值。事实上,moveTo
会创建一条全新的子路径,而lineTo
只是沿着一条已有的子路径继续画线。这条子路径会记录我们所添加的最后一个坐标值,因而可以连续多次调用lineTo
方法。lineTo的每次调用都是从子路径的最后一个坐标值(由moveTo或lineTo调用留下)开始画线,绘制一条线连接lineTo参数所定义的坐标值,然后再将子路径更新到新的坐标值。
绘制三角形的最后一步是调用closePath
方法,它会画一条线连接子路径的最后一个点和第一个点——封闭路径。它也会将起点和终点添加到子路径上,这两个点现在具有相同的坐标值(参见图1)。
贝塞尔曲线
要在画布中绘制一条曲线,我们可以使用arc
方法或arcTo
方法(在两点间绘制一条弧线),但是这些弧线只是一条具有相同半径的曲线。要创建一条更复杂的曲线,需要使用贝塞尔曲线(Bézier curve)方法:quadraticCurveTo
或bezierCurveTo
。
注意:不要被贝塞尔曲线的名称误导,它们都是贝塞尔曲线,即使其中一个方法的名称不包含Bézier字样。实际上,quadraticCurveTo是一种二次贝塞尔曲线,而bezierCurveTo是三次贝塞尔曲线。
这两种贝塞尔曲线都是通过控制点将一条直线变成曲线。二次贝塞尔曲线只有一个控制点,这意味着线条中只有一次弯曲;而三次贝塞尔曲线则有两个控制点,这意味着一条线中会有两次弯曲。通过图2,我们可以直观地了解这两种曲线的效果,左边是二次贝塞尔曲线,右边是三次贝塞尔曲线。
要在画布中创建这两种曲线,只需要直接调用quadraticCurveTo或bezierCurveTo。先尝试使用quadraticCurveTo
:
context.lineWidth = 5; context.beginPath(); context.moveTo(50, 250); context.quadraticCurveTo(250, 100, 450, 250); context.stroke();
除了quadraticCurveTo
方法,这段代码的其他语句你都理解。这个方法有4个参数:控制点的(x,y)坐标值(图2中的cpx和cpy),以及路径目标点的(x,y)坐标。控制点在水平(x)方向上位于线条的中心,在垂直(y)方向上稍微偏上(如图2所示),图3所示的就是这条漂亮的曲线。
创建三次贝塞尔曲线也很简单:
context.lineWidth = 5; context.beginPath(); context.moveTo(50, 250); context.bezierCurveTo(150, 50, 350, 450, 450, 250); context.stroke();
bezierCurveTo
函数有6个参数:第一个控制点的(x,y)坐标值(如图2中的cplx和cply),第二个控制点(x,y)坐标值(cp2x和cp2y),以及路径目标点的(x,y)坐标。两个控制点都位于如图2所示的位置,屏幕上显示了两次弯曲的曲线(参见图4)。
现在绘制的贝塞尔曲线效果已经很不错了,但是在实践中它们一般不单独使用。贝塞尔曲线的最大用处是组合及附加到其他路径上,从而创建一些非常复杂的图形。你不再受限于只能创建直线和简单弧线的路径了!
注意:如果你现在还不理解实际上应该如何处理复杂图形的所有坐标位置,不要担心。Adobe illustrator有一个名为Ai->Canvas的开源插件(https://github.com/mikeswanson/Ai2Canvas),它允许你将绘制好的矢量图转换成画布代码,这意味着你完全不需要计算这些坐标。
$(document).ready(function () { var canvas1 = $("#canvas1"); var context1 = canvas1.get(0).getContext("2d"); context1.beginPath(); context1.moveTo(100, 50); context1.lineTo(150, 150); context1.lineTo(50, 150); context1.closePath(); context1.stroke(); context1.fill(); var canvas2 = $("#canvas2"); var context2 = canvas2.get(0).getContext("2d"); context2.lineWidth = 5; context2.beginPath(); context2.moveTo(50, 250); context2.quadraticCurveTo(250, 100, 450, 250); context2.stroke(); var canvas3 = $("#canvas3"); var context3 = canvas3.get(0).getContext("2d"); context3.lineWidth = 5; context3.beginPath(); context3.moveTo(50, 250); context3.bezierCurveTo(150, 50, 350, 450, 450, 250); context3.stroke(); });