Canvas之路径与绘制

前言

canvas的绘制系统是基于路径的,即先用代码定义路径(看不见的),再描边或者填充
不过canvas中也有两个当即绘制图形的API:strokeReact()与fillReact()(fillText()与strokeText()也能直接绘制,不过绘制的是文字)php

绘制复杂图形的方法都是基于路径html

本文总结了我对canvas路径与绘制实践,主要内容以下html5

  • 路径与子路径概念
  • 经常使用API
  • canvas基于路径的绘制步骤
    • Demo1:使用两次beginPath(),绘制边颜色不一样的直角
    • Demo2:不使用beginPath()会发生什么
    • Demo3:浏览器自动填充与closePath()
    • Demo4:画一个三条边颜色不同的填充三角形
  • 填充路径的“非零环绕规则”
  • 结语
  • 参考资料

Demo我会放在CodePen之中,能够直接打开调试、预览canvas

路径与子路径概念

在某一时刻,canvas之中只能有一条路径存在,Canvas规范将其称为“当前路径“(current path)。然而,这条路径却能够包含许多子路径(subpath)。而子路径,又是由两个或更多的点组成的。
——《HTML5 Canvas核心技术》浏览器

在W3C文档上找到了相关描述:W3C:11 Drawing paths to the canvasbash

The context always has a current default path. There is only one current path, it is not part of the drawing state. The current path is a path, as described above.ide

指出current path 即其文档中描述的path学习

Each object implementing the CanvasPathMethods interface has a path. A path has a list of zero or more subpaths. Each subpath consists of a list of one or more points, connected by straight or curved lines, and a flag indicating whether the subpath is closed or not.ui

连接在此:W3C:5 Building paths
为了方便描述不易混淆,此文就叫作当前路径
当前路径我用图这样理解👇
this

即当前路径 = 子路径3 + 子路径2 + 子路径1

经常使用API

在知道路径与子路径的概念后咱们看看有哪些经常使用的API

API或属性 说明
beginPath() starts a new path by emptying the list of sub-paths.
closePath() attempts to add a straight line from the current point to the start of the current sub-path. If the shape has already been closed or has only one point, this function does nothing.
stroke() strokes (outlines) the current or given path with the current stroke style.
fill() fills the current or given path with the current fillStyle.
moveTo(x, y) begins a new sub-path at the point specified by the given (x, y) coordinates.
lineTo(x, y) adds a straight line to the current sub-path by connecting the sub-path's last point to the specified (x, y) coordinates.
strokeStyle specifies the color, gradient, or pattern to use for the strokes (outlines) around shapes. The default is #000 (black).
lineWidth sets the thickness of lines.
fillStyle specifies the color, gradient, or pattern to use inside shapes. The default style is #000 (black).

API说明来自MDN
由于本文总结的主要是路径与绘制,因此只列举了一些经常使用的API,更多的API诸如arc(),quadraticCurveTo()等可在MDN:Drawing shapes with canvas 发现

这里要注意的是beginPath()moveTo()
beginPath()会清空掉原来的子路径,若是不清空,在屡次调用stroke()时,后面调用的stroke()会再次绘制原先的子路径
moveTo()会开启新的子路径

canvas基于路径的绘制步骤

  1. 使用beginPath()开始绘制路径或清空子路径
  2. 使用lineTo()、moveTo()等API绘制路径
  3. 根据须要是否使用closePath()闭合路径
  4. 根据须要使用stroke()或者fill()等
  5. 根据须要是否使用beginPath()清除当前子路径,开始新的绘制 后面以Demo的形式举例

Demo1:使用两次beginPath(),绘制边颜色不一样的角

CodePen打开

ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.lineWidth = 3;
ctx.moveTo(30, 10); //开始新的子路径
ctx.lineTo(30, 200); //绘制子路径
ctx.stroke();

ctx.beginPath(); //清空子路径
ctx.strokeStyle = "green";
ctx.moveTo(30, 200);//开始新的子路径
ctx.lineTo(230, 200);//绘制子路径
ctx.stroke();
复制代码

第二次调用beginPath()时清空了子路径,并开始画绿线。若是不清空子路径会发生什么呢,看Demo2

Demo2:不使用beginPath()会发生什么

CodePen打开

ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.lineWidth = 3;
ctx.moveTo(30, 10);
ctx.lineTo(30, 200); //绘制子路径
ctx.stroke();

//ctx.beginPath(); 
ctx.strokeStyle = "green";
ctx.moveTo(30, 200);
ctx.lineTo(230, 200);//绘制子路径
ctx.stroke();
复制代码

能够看到,粉色线不见了,实际上是被绿线覆盖了。由于原先的子路径并无被清除,因此第二次调用stroke()时,图中有两条子路径,故绘制时把原来的粉线覆盖。
到这里大概能够理解路径与子路径及beginPath()的做用了吧

Demo3:浏览器自动填充与closePath()

路径分为封闭路径开放路径,可是不管是封闭路径仍是开放路径均可以进行填充,当填充开放路径时,浏览器会将其看成封闭路径来填充
浏览器自动填充Demo👇:

CodePen打开

ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.lineWidth = 5;
ctx.moveTo(30, 10);
ctx.lineTo(30, 200);
ctx.lineTo(230, 200);//绘制子路径
// ctx.closePath();
ctx.stroke();
ctx.fill();
复制代码

能够看到没有粉色斜边被stroke(),但fill()时浏览器进行了自动填充
若是是closePath()或者手动lineTo()就会有对应的边

CodePen打开

ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.lineWidth = 5;
ctx.moveTo(30, 10);
ctx.lineTo(30, 200);
ctx.lineTo(230, 200);//绘制子路径
// ctx.closePath()或者lineTo()
// ctx.closePath()
ctx.lineTo(30,10);
ctx.stroke();
ctx.fill();
复制代码

Demo4:画一个三条边颜色不同的填充三角形

经过上面实践(Demo2),咱们发现不能在当前路径中绘制不一样颜色的边,同时beginPath()会清空子路径以重置当前路径,因此咱们没办法在不覆盖边的状况下一鼓作气fill()一个三角形,须要调用四次beginPath()(三次用于画边,一次用于三角形路径并填充;若是在能够覆盖边的状况下,三次beginPath()便可,在一次fill()时绘制边,后面绘制边时进行覆盖)

CodePen打开

// 画粉边框
ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.moveTo(30, 10);
ctx.lineTo(30, 200);
ctx.stroke();

//画绿边框
ctx.beginPath();
ctx.strokeStyle = "green";
ctx.moveTo(30, 200);
ctx.lineTo(210, 200);//绘制子路径
ctx.stroke();

//画蓝边框
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.moveTo(210, 200);
ctx.lineTo(30, 10);//绘制子路径
ctx.stroke();

//三角形路径绘制,并填充
ctx.beginPath();
ctx.fillStyle="yellow";
ctx.moveTo(30, 10);//开始一条子路径
ctx.lineTo(30, 200);
ctx.lineTo(210, 200);
ctx.closePath();
ctx.fill();
复制代码

填充路径的“非零环绕规则”

若是当前路径有链接造成多个闭环或者各个子路径相交造成多个闭环,在进行fill()填充时,会使用”非零环绕规则“来判断如何进行填充

如上图,是填充外边的环,仍是填充里面的三角形?
如下是”非零环绕规则“的释义

”非零环绕规则“是这么来判断有自我交叉状况的路径的:对于路径中的任意给定区域,从该区域内部画一条足够长的线段,使此线段的终点彻底落在路径范围以外。接下来,将计数器初始化为0,而后,每当这条线段与路径上的直线或曲线相交时,就改变计数器的值。若是是与路径的顺时针部分相交,则加1,若是是与路径的逆时针部分相交,则减一。若计数器的最终值不是0,那么此区域就在路径里面,在调用fill()方法时,浏览器就会对其进行填充。若是最终值是0,那么此区域就不在路径内部,浏览器也就不会对其进行填充了 ——《HTML5 Canvas核心技术》

下面举个例子,图自Wikipedia

区域1向外画箭头,通过一个逆时针的路径,计数器-1,再通过一个顺时针的路径,计数器+1,最终为0,因此区域1不填充
区域2向外画箭头,通过一个顺时针的路径,计数器+1,又通过一个顺时针的路径,计数器+1,最终为2,不为0,因此区域2填充
最后只要是计数器不为0便可进行填充
拿上面三角形的例子继续举例

因此均有填充,图片及对应代码以下

(注意fill()与stroke()的调用顺序,若是fill()在后,会把stroke()后的部分描边覆盖掉)
CodePen打开

CodePen打开

结语

本文经过一些基础的API讲述了路径与绘制,还有其余一些诸如arc()等创造子路径的API能够去MDN查看。
若有不对欢迎交流学习✨

参考资料

HTML5 Canvas path tutorial
MDN:Drawing shapes with canvas
W3C:5 Building paths
W3C:11 Drawing paths to the canvas

相关文章
相关标签/搜索