裁剪
canvas中有一个功能是裁剪,通过裁剪功能,可以实现裁剪区域。裁剪区域是canvas中由路径(可以称之为裁剪路径)所定义的一块区域,所有的绘图操作都限制在本区域内,区域之外的绘制效果会被忽略。默认情况下,可以认为剪辑区域就是canvas的整个绘图表面。除非你创建路径并调用clip()函数来显式的设定剪辑区域,否则默认的剪辑区域不会影响canvas中所绘制的内容。一旦设置了剪辑区域,在canvas中绘制的所有内容都将局限在剪辑区域内,这也意味着在剪辑区域以外进行绘制是没有任何效果的。
canvas的裁剪路径就是普通的绘制图形的路径,不同的是裁剪路径的作用不是用于绘制,而是用来形成裁剪效果(这有点像Flash中的图层遮罩)。如下图所示,红边五角星就是裁切路径,所有在路径以外的部分都不会在canvas上绘制出来。
五角星裁剪
裁剪函数clip
可以使用context.clip函数来实现裁剪功能。clip函数有两个版本:
ctx.clip([fillRule]);
ctx.clip(path [, fillRule]);
第一个版本,使用当前路径作为裁剪的路径;而第二个版本,使用指定的路径对象path(Path2D对象)作为裁剪的路径。
下面看一个示例:
ctx.rect(300,300,100,100);
ctx.stroke();
ctx.clip();
ctx.fillStyle = 'blue';
ctx.fillRect(200, 200, 200, 200);
代码首先定义一个rect路径起点为(300,300),宽高均为100,然后调用stroke方法绘制描边效果,再调用clip方法把路径围成的区域设置为裁剪区域,然后使用蓝色绘制一个矩形,矩形从(200,200)开始,宽高均为200。最终绘制效果如下:
裁剪绘制效果
可以看出只有在裁剪区域里面的部分被绘制出来。
取消裁剪
一旦设置了裁剪区域,则之后所有的绘图都会被限制在裁剪区域之类。如果想要取消裁剪区域,需要使用save&restore函数。save&restore函数是取消裁剪区域的唯一方法。
在调用clip函数之前,先调用save函数,保存当前的绘制状态。
然后调用clip函数设置裁剪区域,并在裁剪区域里面绘制图形。
然后调用restore函数,恢复绘制状态,设置的裁剪区域会被取消。
改进前面的示例代码:
ctx.save();
ctx.rect(300, 300, 100, 100);
ctx.lineWidth = 4;
ctx.stroke();
ctx.clip();
ctx.fillStyle = 'blue';
ctx.fillRect(200, 200, 200, 200);
ctx.restore();
ctx.fillStyle = 'blue';
ctx.fillRect(400, 400, 200, 200);
在之前示例的基础上,加上save&restore函数。 restore函数之后,再绘制一个矩形,可以看出restore之后绘制的矩形,不再被限制到之前的裁剪区域之中,如下图所示:
取消裁剪
裁剪的组合
前面的示例中演示了单个路径组成的裁剪区域。如果有多个路径的情况下,裁剪区域将会如何?事实上,多个路径可以形成并集和交集的结果。
并集
要形成并集的效果,只需要把多个路径组织在一起,然后统一clip即可。在之前的文章《深入理解路径(Path)》中,我们学习了路径和子路径的概念,只需要把多个路径以子路径的方式组织成一个路径,就可以很方便的形成多个路径的并集,在这个路径并集的基础之上,调用clip函数,就可以得到一个合并的裁剪区域。比如如下代码:
ctx.beginPath();
ctx.arc(300,300,100,0,Math.PI*2);
ctx.rect(300,300,100,100);
ctx.clip();
ctx.beginPath();
ctx.fillStyle = 'blue';
ctx.fillRect(0,0,1000,1000);
首先调用beginPath开启新的当前路径。然后调用arc和rect函数分别在当前路径上添加两条子路径,之后调用clip函数,此时裁剪区域是两条子路径形成的并集。 然后在此裁剪区域上面进行填充,最终效果如下:
并集
交集
clip函数从当前的可绘制区域裁剪出一块区域来,这块区域就是裁剪区域。
如果裁剪的区域超过了canvas的绘制表面的区域范围,那么绘制的图形不会被显示出来。
如果两次调用clip函数,那么第二次的裁剪区域是在第一次裁剪区域的基础之上裁剪出来的区域,也就是说,如果两次调用clip函数,最终裁剪区域是两次裁剪区域的交集。
示例代码如下:
ctx.beginPath();
ctx.arc(300,300,100,0,Math.PI*2);
ctx.clip();
ctx.beginPath();
ctx.rect(300,300,100,100);
ctx.clip();
ctx.beginPath();
ctx.fillStyle = 'blue';
ctx.fillRect(0,0,1000,1000);
首先调用arc函数确定一个圆形路径,并调用clip函数裁剪,然后调用rect函数确定一个矩形,并再次调用clip函数裁剪。然后在裁剪区域上面进行填充,最终的效果如下:
交集
从图中可以看出,最终的裁剪区域是两次裁剪区域的交集。
反向裁剪
前面的裁剪区域都是正向的,所谓“正向”是指,我们指定一个路径围成的区域,调用clip方法,该区域就变成裁剪区域。 所谓“反向裁剪”是指,我们指定一个路径围成的区域,通过某些操作,该区域之外的区域变成裁剪区域。
有什么办法可以实现先反向裁剪呢?
利用非零环绕原则
在之前的文章《深入理解路径(Path)》中,讲述过非零环绕原则,如果读者还不太明白,可以去看这篇文章。
我们知道利用非零环绕原则,通过两个相反方向(顺时针和逆时针)的路径,可以实现挖空的效果。
应此,利用非零环绕原则实现方向裁剪的大致思路如下:
首先构建一个大的区域路径(顺时针方向),比如矩形
然后构建一个小的区域路径(逆时针方向),比如圆形
调用clip函数裁剪,然后绘制图形。
示例代码如下:
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height); //顺时针方向
ctx.arc(100, 100, 50, 0, Math.PI * 2, true); // 逆时针方向
ctx.clip();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'red';
ctx.fill();
最终绘制的效果如下图所示:
反向裁剪
使用clip + clearRect方法
大概的思路如下:
首先绘制要绘制的图形(比如矩形)
然后设置要反向裁剪的图形的路径(比如圆形)
然后调用clip ,再调用clearRect方法清除圆形区域的像素。
示例代码如下:
```
ctx.beginPath();