这篇将要介绍一下Canvas中的基本图形。
图形的基础-路径
在Canvas中,所有基本图形都是以路径为基础的,也就是说,我们在调用2dContext的lineTo、rect等方法时,其实就是往已经的context路径集合中再添加一些路径点,在最后使用fill或stroke方法进行绘制时,都是依据这些路径点来进行填充或画线。
在每次开始绘制路径前,都应该使用context.beginPath()方法来告诉Context对象开始绘制一个新的路径,否则接下来绘制的路径会与之前绘制的路径叠加,在填充或画边框时就会出现问题。在绘制完成路径后,可以直接使用context.closePath()方法来关闭路径,或者手动关闭路径。另外,如果在填充时路径没有关闭,那么Context会自动调用closePath方法将路径关闭。
基本路径方法
1.beginPath,closePath
这两个方法在前面已经介绍过,分别用来通知Context开始一个新的路径和关闭当前的路径。
在Canvas中使用路径时,应该要保持一个良好的习惯,每次开始绘制路径前都要调用一次beginPath方法,否则画出来的效果难看不说,还会严重影响性能。
在下面这张图中,左边的图形在每次绘制矩形前都调用了一次beginPath来清除之前的路径并重新开始绘制新的路径,而后面的图形则就只在绘制所有图形前调用了一次beginPath来清除路径,因此,虽然这里是使用的边框色是#666,但是右边的图形颜色比左边的深一些,因为每次使用stroke绘制边框时,会把之前的路径再次绘制一遍,叠加起来颜色就比原来深一些。
<canvas id="canvas" width="500" height="500"></canvas> <script type="text/javascript"> var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.strokeStyle = "#666"; function useBeginPath() { for (var i = 0; i < 5; ++i) { ctx.beginPath(); ctx.rect(10 + i*20, 10 + i*20, 210 - i*40, 210 - i*40); ctx.stroke(); } } function notUseBeginPath() { ctx.beginPath(); for (var i = 0; i < 5; ++i) { ctx.rect(240 + i*20, 10 + i*20, 210 - i*40, 210 - i*40); ctx.stroke(); } } useBeginPath(); notUseBeginPath(); </script>
提示:您可以先修改部分代码再运行
在Context中路径数较少时,如果不考虑显示效果,性能上还可以接受,但是如果Context中的路径数很多时,在开始绘制新路径前不使用beginPath的话,因为每次绘制都要将之前的路径重新绘制一遍,这时性能会以指数下降。
因此,除非有特殊需要,每次开始绘制路径前都要调用beginPath来开始新路径。
2.移动与直线moveTo,lineTo,rect
<canvas id="canvas" width="500" height="500"></canvas> <script type="text/javascript"> var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(10, 10); ctx.lineTo(110,110); ctx.lineTo(10, 110); ctx.lineTo(10, 10); ctx.stroke(); ctx.beginPath(); ctx.rect(120, 10, 100, 100); ctx.stroke(); </script>
提示:您可以先修改部分代码再运行
voidmoveTo(infloatx,infloaty);
在Canvas中绘制路径,一般是不需要指定起点的,默认的起点就是上一次绘制路径的终点,因此,如果需要指定起点的话,就需要使用moveTo方法来指定要移动到的位置。
voidlineTo(infloatx,infloaty);
lineTo方法则是绘制一条直接路径到指定的位置。在调用完lineTo方法后,Context内部的绘制起点会移动到直线的终点。
voidrect(infloatx,infloaty,infloatw,infloath);
rect方法用来绘制一个矩形路径,通过参数指定左上角位置以及宽和高。在调用rect后,Context的绘制起点会移动到rect绘制的矩形的左上角。
rect方法与后面要介绍的arc方法与其他路径方法有一点不同,它们是使用参数指定起点的,而不是使用Context内部维护的起点。
3.曲线arcTo,arc,quadraticCurveTo,bezierCurveTo
voidarcTo(infloatx1,infloaty1,infloatx2,infloaty2,infloatradius);
按照WHATWG文档的说明,这个方法是画一个与两条射线相切的的圆弧,两条射线其中一条为穿过Context绘制起点,终点为(x1,y1),另外一条为穿过(x2,y2),终点为(x1,y1),这条圆弧为最小的与这两条射线相切的圆弧。在调用完arcTo方法后,将圆弧与射线(x1,y1)-(x2,y2)的切点添加到当前路径中,做为下次绘制的起点。
在测试中发现,Firefox和Opera目前对这个方法的支持并不好,只有Chrome和Safari4能绘制出正确的路径。
图中的的两条灰色直线是偏移4个像素后的两条射线所在的位置。
<canvas id="canvas" width="500" height="500"></canvas> <script type="text/javascript"> var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.strokeStyle = "#000"; ctx.translate(200, 200); ctx.moveTo(10, 10); ctx.arcTo(110, 60, 10, 110, 30); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = "#999"; ctx.moveTo(10, 6); ctx.lineTo(114, 60); ctx.lineTo(10, 114); ctx.stroke(); </script>
提示:您可以先修改部分代码再运行
voidarc(infloatx,infloaty,infloatradius,infloatstartAngle,infloatendAngle,inbooleananticlockwise);
arc方法用来绘制一段圆弧路径,通过圆心位置、起始弧度、终止弧度来指定圆弧的位置和大小,这个方法也不依赖于Context维护的绘制起点。而在画圆弧时的旋转方向则由最后一个参数anticlockwise来指定,如果为true就是逆时针,false则为顺时针。
voidquadraticCurveTo(infloatcpx,infloatcpy,infloatx,infloaty);
quadraticCurveTo方法用来绘制二次样条曲线路径,参数中cpx与cpy指定控制点的位置,x和y指定终点的位置,起点则是由Context维护的绘制起点。
voidbezierCurveTo(infloatcp1x,infloatcp1y,infloatcp2x,infloatcp2y,infloatx,infloaty);
bezierCurveTo方法用来绘制贝塞尔曲线路径,它与quadraticCurveTo相似,不过贝塞尔曲线有两个控制点,因此参数中的cp1x,cp1y,cp2x,cp2y用来指定两个控制点的位置,而x和y指定绺的位置。
<canvas id="canvas" width="500" height="500"></canvas> <script type="text/javascript"> var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.translate(10, 10); ctx.beginPath(); ctx.arc(50, 50, 50, 0, Math.PI, true); ctx.stroke(); // quadraticCurveTo ctx.beginPath(); ctx.strokeStyle = "#000"; ctx.moveTo(110, 50); ctx.quadraticCurveTo(160, 0, 210, 50); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = "red"; ctx.moveTo(110, 50); ctx.lineTo(160, 0); ctx.lineTo(210, 50); ctx.stroke(); // bezierCurveTo ctx.beginPath(); ctx.strokeStyle = "#000"; ctx.moveTo(220, 50); ctx.bezierCurveTo(250, 0, 280, 10, 320, 50); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = "red"; ctx.moveTo(220, 50); ctx.lineTo(250, 0); ctx.lineTo(280, 10); ctx.lineTo(320, 50); ctx.stroke(); </script>
提示:您可以先修改部分代码再运行
4.fill,stroke,clip
fill与stroke这两个方法很好理解,分别用来填充路径与绘制路径线条。
clip方法用来给Canvas设置一个剪辑区域,在调用clip方法之后的代码只对这个设定的剪辑区域有效,不会影响其他地方,这个方法在要进行局部更新时很有用。默认情况下,剪辑区域是一个左上角在(0,0),宽和高分别等于Canvas元素的宽和高的矩形。
在画这个图时,虽然两次都是使用fillRect(0,0,100,100)填充了一个100x100大小矩形,但是显示的结果却是第二次填充的只是中间的一小块,这是因为在两次填充之间使用clip方法设定了剪辑区域,这样第二次填充时只会影响到所设定的中间那一小部分区域。
<canvas id="canvas" width="500" height="500"></canvas> <script type="text/javascript"> var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.translate(10, 10); // fill a green rectangle ctx.fillStyle = "green"; ctx.fillRect(0, 0, 100, 100); // set the clipping region ctx.beginPath(); ctx.rect(30, 30, 40, 40); ctx.clip(); ctx.stroke(); // fill a yellow rectangle ctx.fillStyle = "yellow"; ctx.fillRect(0, 0, 100, 100); </script>
提示:您可以先修改部分代码再运行
5.clearRect,fillRect,strokeRect
这三个方法并不是路径方法,而是用来直接处理Canvas上的内容,相当于Canvas的背景,调用这三个方法也不会影响Context绘图的起点。
要清除Canvas上的所有内容时,可以直接调用context.clearRect(0,0,width,height)来直接清除,而不需要使用路径方法绘制一个与Canvas同等大小的矩形路径再使用fill方法去清除。
结语
通过Canvas的路径方法,可以使用Canvas处理一些简单的矢量图形,这样在缩放时也不会失真。不过Canvas的路径方法也不是很强大,至少连个椭圆的路径都没有……
这篇写得有点长了,Cnavas中路径相关的内容就写这么多,后面再讲讲Canvas其他的东西。
参考资料
1.TheCanvasElement,WHATWG