1

又一个项目最忙的阶段过去了,回头看看自己学canvas留下的半拉子文章,再一次证明技术不用是会忘记的,好多东西都没什么印象了T_T。
不想烂尾,再啃一遍书~

此为绘图第二部分:路径绘制

用canvas绘制简单的图形,有点像用一支笔在纸上画画。通过不停调整落笔的位置、画各种各样的线条来勾勒出画的框架,再用各种颜色对封闭区间填充、对线条描边。在canvas中绘图的过程基本和这差不多。

canvas里有路径的概念。可以理解成通过画笔画出的任意线条,这些线条甚至不用相连。在没描边(stroke)或是填充(fill)之前,路径在canvas画布上是看不到的。

CanvasRenderingContext2D提供了一系列方法来绘制路径

moveTo

context.moveTo(double x, double y)

将当前位置移动到坐标(x, y)。

lineTo

context.lineTo(double x, double y)

从当前位置向坐标(x, y)画一条直线路径。如果不存在当前位置,相当于执行moveTo(x, y)(在崭新的路径中没有执行过任何操作的情况下,默认是不存在当前位置的,所以一般在执行lineTo()之前,先执行moveTo())

stroke

context.stroke()

对当前路径中的线段或曲线进行描边。描边的颜色由strokeStyle决定,描边的粗细由lineWidth决定。另外与stroke相关的属性还有lineCap、lineJoin、miterLimit。

属性 说明
lineWidth 该值决定了再canvas中绘制线段的屏幕像素宽度。必须是个非负、非无穷的double值,默认值为1.0
strokeStyle 指定了对路径进行描边时所用的绘制风格,可以被设定成某个颜色、渐变、或者图案(渐变和图案后面再说,这篇只用到设置颜色)
lineCap 设置如何绘制线段的端点。有三个值可选:butt、round、和square。默认为butt
lineJoin 设置同一个路径中相连线段的交汇处如何绘制。有三个值可选:bevel、round、miter。默认为miter
miterLimit 当lineJoin设置为miter时有效,该属性设置两条线段交汇处最大渲染长度。

后面三个属性和svg中的stroke-linecap、stroke-linejoin和stroke-miterlimit用途完全一致。

beginPath

context.beginPath();

在说明beginPath用途前先了解路径这一概念。在任意时刻,canvas中只能有一条路径存在,被称为"当前路径"(current path)。对一条路径进行描边(stroke)时,这条路径的所有线段、曲线都会被描边成指定颜色。这意味着,如果在同一路径上先画了条直线,描边成红色,再画一条曲线,再描边成黑色时,整条路径上的线都会用黑色再次描边,包括之前已经描成红色的直线。

比如想画一条垂直黑线和一条水平红线:

var context = document.getElementById("canvas").getContext("2d");
context.lineWidth = 4;
// 画一条垂直黑色线段
context.strokeStyle = "black";
context.moveTo(100, 10);
context.lineTo(100, 100);
context.stroke();
// 画一条水平红色线段
context.strokeStyle = "red";
context.lineTo(190, 100);
context.stroke();

最后的结果呈现

clipboard.png

这并不是想要的结果,垂直黑线被用红色又描了一次边。
好在可以在当前路径上创建更多的“子路径”(subpath),让在当前子路径上的绘制不对之前的路径产生影响。这就是beginPath()的作用。

var context = document.getElementById("canvas").getContext("2d");
context.strokeStyle = "black";
context.lineWidth = 4;
context.lineCap = "square";
// 画一条垂直黑色线段
context.beginPath();
context.moveTo(100, 10);
context.lineTo(100, 100);
context.stroke();
// 画一条水平红色线段
context.beginPath();
context.moveTo(100, 100);
context.lineTo(190, 100);
context.strokeStyle = "red";
context.stroke();

clipboard.png

这里使用context.lineCap= "square"可以让两条线段看上去是相连的。如果不设置lineCap属性,两条线段交汇处是这个样子的:

clipboard.png

closePath

context.closePath();

当路径中的起始点和终止点不在同一点上时,执行closePath()会用一条直线将起始点和终止点相连。

var context = document.getElementById("canvas").getContext("2d");
context.strokeStyle = "black";
context.lineWidth = 10;
context.moveTo(10, 10);
context.lineTo(100, 10);
context.lineTo(100, 100);
context.lineTo(10, 100);
context.closePath();
context.stroke();

clipboard.png

与svg略有不同的是,在canvas中绘制路径时,如果起始点与终止点在同一点上时,canvas会对交汇处自动做连接处理,但svg不会。

arcTo

context.arcTo(double pointX1, double pointY1, double pointX2, double pointY2, double radius)

首先从当前位置向(pointX1, pointY1)做条辅助线l1,再从(pointX1, pointY1)向(pointX2, pointY2)做条辅助线l2,然后以radius为半径,画一条与l1和l2都相切的曲线。
这种方式做曲线需要注意的是,曲线终点坐标有时会变得非常难算,而且曲线的起点和当前路径的起点也不一定重合,在曲线起点和当前路径起点不重合时,canvas会先从路径起点向曲线起点做一条直线,然后再画曲线。

var context = document.getElementById("canvas").getContext("2d");
context.strokeStyle = "black";
context.lineWidth = 4;
context.moveTo(100, 100);
context.arcTo(300, 100, 300, 300, 150);
context.stroke();

clipboard.png

粗线为arcTo绘制的曲线

quadraticCurveTo & bezierCurveTo

context.quadraticCurveTo(cx, cy, x, y);
context.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);

二次贝塞尔曲线和三次贝塞尔曲线的绘制方法在svg之Paths的贝塞尔曲线中已经写得比较清楚了。canvas中绘制二次贝塞尔曲线方法quadraticCurveTo的四个参数,即控制点的坐标(cx, cy),以及终点坐标(x, y);三次贝塞尔曲线方法bezierCurveTo的六个参数,即两个控制点的坐标(cx1, cy1)和(cx2, cy2),以及终点坐标(x, y)。

var context = document.getElementById("canvas").getContext("2d");
context.beginPath();
context.moveTo(10, 50);
context.quadraticCurveTo(30, 100, 100, 50);
context.bezierCurveTo(170, 0, 170, 100, 250, 50)
context.stroke();

clipboard.png

svg中除了提供绘制贝塞尔曲线的Q和C以外,还提供了T让曲线可以平滑收尾(实际就是替我们把最后一个控制点坐标计算好了)。canvas中似乎暂时没有这么贴心的方法。上图中二次贝塞尔曲线和三次贝塞尔曲线的交接处能如此平滑,就只能靠人工去计算三次贝塞尔曲线的第一个控制点了。

控制点的计算公式为:
x2 = x + (x - x1) = 2 * x - x1
y2 = y + (y - y1) = 2 * y - y1

(x, y)为上一条曲线的终点坐标,(x1, y1)为上一条曲线最后一个控制点坐标。(x2, y2)即我们要计算的控制点坐标了。以上图为例,(x, y)=(100, 50);(x1, y1)=(30, 100), 计算得出(x2, y2)=(170, 0)

rect

和矩形相关的方法canvas提供了四种:

strokeRect(x, y, w, h);

以(x, y)为矩形左上角坐标点,w为宽度,h为高度,绘制矩形并描边。

fillRect(x, y, w, h);

以(x, y)为矩形左上角坐标点,w为宽度,h为高度,绘制矩形并填充。

rect(x, y, w, h);

绘制一个矩形路径(只绘制路径,并不做填充或描边),以(x, y)作为矩形左上角坐标,w为宽度,h为高度。
rect()与strokeRect()/fillRect()的另一个本质区别是,rect()绘制的矩形是带路径信息的,相当于以左上角为起点画一个矩形,最后终点依然回到左上角,接着rect()绘制的路径是以矩形左上角坐标为起点的。但strokeRect()和fillRect()是独立与当前路径的:

var context = document.getElementById("canvas").getContext("2d");
context.beginPath();
context.moveTo(200, 100);
context.strokeRect(10,10,200,50);
context.lineTo(100, 100);
context.strokeStyle="red"
context.stroke();

clipboard.png

lineTo()绘制的线段起点还是(200,100),并不受strokeRect()影响,而矩形的绘制颜色也没有受到strokeStyle="red"的影响。
再看rect()方法:

var context = document.getElementById("canvas").getContext("2d");
context.beginPath();
context.moveTo(200, 100);
context.rect(10,10,200,50);
context.lineTo(100, 100);
context.strokeStyle="red"
context.stroke();

clipboard.png

差异显而易见。

clearRect(x, y, w, h);

将制定矩形与当前剪辑区域相交范围内的所有像素清除。默认情况下,剪辑区域大小就是整个canvas画布(剪辑区域的具体内容下篇整理)。所谓“清除像素”,指的是将其颜色设置为全透明的黑色,实际效果上等同于擦除了某个像素,从而使得canvas的背景可以透过该像素显示出来。

arc

context.arc(cx, cy, r, startAngle, endAngle, counterClockwise);
以(cx, cy)为圆心,r为半径,startAngle为起始角度,endAngle为终止角度画圆弧,counterClockwise用于规定画圆弧的方向,true为逆时针,false为顺时针,默认为false。

关于角度的问题,哪个方向是0,哪个方向是Math.PI,目前看起来svg和canvas都很统一,即如下图所示。

clipboard.png

值得注意的是,arc()和rect()一样,只绘制路径,并将其终点作为当前路径终点。但不同之处在于,arc()的起点如果与当前路径终点不在同一个点的情况下,绘制圆弧前,canvas会从当前路径终点向arc()起点做一条线段,然后再开始绘制圆弧。而rect()则会跳过这一步直接绘制矩形。

var context = document.getElementById("canvas").getContext("2d");
context.strokeStyle = "black";
context.moveTo(context.canvas.width / 2, context.canvas.height / 2);
context.arc(context.canvas.width / 2, context.canvas.height / 2, 100, 0, Math.PI * 3 / 2, false);
context.stroke();

clipboard.png

var context = document.getElementById("canvas").getContext("2d");
context.beginPath();
context.moveTo(200, 200);
context.rect(100, 100, 100, 100);
context.stroke();

clipboard.png

可以看到,矩形图中并没有绘制一条从(200,200)到(100,100)的线段,而圆弧图中从圆心到圆弧起始点之间,绘制了一条线段。

fill

context.fill();

填充当前路径。无论当前路径时封闭还是开放,浏览器都会将其当成封闭路径来填充,就好像填充前先执行了一次context.closePath()。
可以通过设置fillStyle属性,指定后续图形填充操作中使用的颜色、渐变色或图案。
与svg一样,canvas填充也可以指定fillStyle是evenodd或者nonzero。默认填充方式为nonzero。关于填充方式,依然参考这篇http://m.blog.csdn.net/mishiw...

var context = document.getElementById("canvas").getContext("2d");
context.fillStyle = "red";
context.lineWidth = 4;
context.arc(context.canvas.width / 2, context.canvas.height / 2, 100, 0, Math.PI * 2, false);
context.arc(context.canvas.width / 2, context.canvas.height / 2, 50, 0, Math.PI * 2, true);
context.stroke();
context.fill();

clipboard.png

var context = document.getElementById("canvas").getContext("2d");
context.fillStyle = "red";
context.lineWidth = 4;
context.arc(context.canvas.width / 2, context.canvas.height / 2, 100, 0, Math.PI * 2, false);
context.arc(context.canvas.width / 2, context.canvas.height / 2, 50, 0, Math.PI * 2, false);
context.stroke();
context.fill();

clipboard.png


梦梦她爹
1.8k 声望122 粉丝

引用和评论

0 条评论