@@ -223,25 +223,65 @@ const ctx = canvas.getContext('2d', {
223
223
224
224
Canvas提供了` globalCompositeOperation ` 属性来实现混合模式。
225
225
226
+
227
+
226
228
### 裁剪Clip(Mask)
227
229
228
- 在路径绘制一节中我们介绍了如果创建路径 ,并通过` fill() ` 或者` stroke() ` 来绘制路径 ,除了这两个方法外我们还可以使用` clip() ` 来创建一个裁剪路径,后续的绘制命令都只会绘制到裁剪路径所圈出的范围内。 通过这个方法可以实现遮罩Mask的效果。
230
+ 在路径绘制一节中我们介绍了如何创建路径 ,并通过` fill() ` 或者` stroke() ` 来填充路径或描边路径 ,除了这两个方法外我们还可以使用` clip() ` 来创建一个裁剪路径。后续的绘制命令都只会绘制到裁剪路径所圈出的 ** 范围内 ** , 通过这个方法可以实现遮罩Mask的效果。
229
231
230
232
``` js
231
233
ctx .save ()
232
234
ctx .beginPath ();
233
- ctx .moveTo (0 , 0 )
234
- ctx .lineTo (100 , 0 )
235
- ctx .lineTo (0 , 100 )
235
+ ctx .arc (100 , 100 , 50 , 0 , Math .PI * 2 )
236
236
ctx .clip ();
237
-
238
237
ctx .fillStyle = ' pink'
239
- ctx .fillRect (0 , 0 , 200 , 200 )
238
+ ctx .fillRect (0 , 0 , 100 , 100 )
239
+ ctx .restore () // 不恢复的话后续其他绘制都只能在clip区执行了
240
+
241
+ ctx .fillStyle = ' skyblue'
242
+ ctx .fillRect (100 , 100 , 100 , 100 )
243
+ ```
244
+
245
+ 在这个例子中我们创建了一个圆形的裁剪路径,后续绘制的粉色矩形会被裁剪到只展示圆形裁剪路径内的内容。
246
+
247
+
248
+
249
+ #### 路径环绕规则
250
+
251
+ 通过上面的例子,我们知道在Canvas中通过裁剪来实现遮罩效果是很简单的;我们可以更进一步,思考一下如何实现** 反向的裁剪区域** ,即只绘制圆形裁剪路径之外的内容?
252
+
253
+ Canvas的` fill ` API能够填充路径的** 内部区域** ,` clip ` 能够把路径的** 内部区域** 视为裁剪区域。当一个路径包含多个区域时,我们怎么分辨某个区域是属于路径的内部还是外部呢?这是通过内部的** 路径环绕规则** 来决定的,` fill ` 和` clip ` 这两个API都支持传入参数来指定路径环绕规则,分别是默认的** 非零环绕规则nonzero** 以及** 奇偶环绕规则evenodd** 。在图形学中,这个规则可以用来判断一个点是否在多边形(路径)的内部来进行点击/碰撞计算。
254
+
240
255
256
+
257
+ ** 非零环绕规则nonzero**
258
+
259
+ Canvas默认使用非零环绕规则。简单来说,对于区域内的任意点向外无限远引一条射线,射线会经过若干条路径,假如其中两条路径是顺时针环绕的,另一条路径是逆时针环绕的,两种环绕的差值不为零,那么说明这个区域是在** 路径内部的** 。当使用` fill ` 时,这个区域会被填充;当使用` clip ` 时,这个区域会被视为裁剪区域。
260
+
261
+
262
+
263
+ ** 奇偶环绕规则evenodd**
264
+
265
+ 与非零环绕规则不同的是,奇偶环绕规则无视了路径的环绕方向(顺时针或逆时针)。对于区域内的任意点向外无限远引一条射线,射线如果总是经过奇数条路径,则该区域在路径内部;否则区域在路径外部。
266
+
267
+
268
+
269
+ 我们现在了解了` clip ` 默认使用的非零环绕规则的原理,那么如何实现我们最初的目标“反向裁剪”?事实上,我们可以先绘制一个顺时针方向的矩形,再在内部绘制一个逆时针反向的圆形,这样通过非零环绕规则的计算这两个图形中间的区域会被视为路径的内部,成为了裁剪区域;而圆形内部的区域,则会被视为路径的外部,不会再被视为裁剪区域。
270
+
271
+ ``` js
272
+ const ctx = canvas .getContext (' 2d' );
273
+ ctx .save ()
274
+ ctx .beginPath ();
275
+
276
+ ctx .rect (0 , 0 , 200 , 200 ) // 先顺时针绘制矩形
277
+ ctx .arc (100 , 100 , 50 , 0 , Math .PI * 2 , true ) // 通过传入true来逆时针绘制圆形
278
+ ctx .clip ();
279
+ ctx .fillStyle = ' pink'
280
+ ctx .fillRect (0 , 0 , 100 , 100 )
241
281
ctx .restore () // 不恢复的话后续其他绘制都只能在clip区执行了
242
282
243
283
ctx .fillStyle = ' skyblue'
244
- ctx .fillRect (50 , 50 , 200 , 200 )
284
+ ctx .fillRect (100 , 100 , 100 , 100 )
245
285
```
246
286
247
287
0 commit comments