博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
canvas之路径绘制
阅读量:6267 次
发布时间:2019-06-22

本文共 6608 字,大约阅读时间需要 22 分钟。

又一个项目最忙的阶段过去了,回头看看自己学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时有效,该属性设置两条线段交汇处最大渲染长度。

后面三个属性和用途完全一致。

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);

二次贝塞尔曲线和三次贝塞尔曲线的绘制方法在中已经写得比较清楚了。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。关于填充方式,依然参考这篇

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

转载地址:http://qjkpa.baihongyu.com/

你可能感兴趣的文章
chrome浏览器:chrome 69 恢复默认UI
查看>>
Irony - 一个 .NET 语言实现工具包
查看>>
Java之Static静态修饰符详解
查看>>
修改weblogic部署的应用名称
查看>>
aaronyang的百度地图API之LBS云与.NET开发 Javascript API 2.0【基本地图的操作】
查看>>
Java Nio 多线程网络下载
查看>>
C++不让程序一闪而过
查看>>
C# 中的枚举类型 enum (属于值类型)
查看>>
[Debug] Use Snippets to Store Behaviors in Chrome DevTools
查看>>
【Java面试题】3 Java的"=="和equals方法究竟有什么区别?简单解释,很清楚
查看>>
通用性好的win2003序列号: (推荐先用这个里面的)
查看>>
Chromium Embedded Framework中文文档 (升级到最新的Chrome)
查看>>
WPF Command CanExecute 的执行逻辑
查看>>
更为快捷的Excel操作方式 快捷键 Alt使用技巧动画图解
查看>>
程序员们最易犯的10种错误
查看>>
面试必考题!你知道CSS实现水平垂直居中的第10种方式吗?
查看>>
超多惊喜!苹果 iPhone8 最新渲染图曝光
查看>>
你想要不想要?OPPO R11将搭配前后2000万像素镜头!
查看>>
Payara基金会发布全面支持MicroProfile 2.0的5.183版
查看>>
360金融宣布采用新会计准则 2018年前三季度净利11亿
查看>>