上一节中学习了Canvas的图像合成,通过图像合成的技术可以轻易的实现类似于刮刮卡的效果,其实除了实现刮刮卡的效果之外,还可以使用这种技术制作类似于探照灯的效果。
前面也说过了,Canvas中的图像合成非常类似于CSS中的混合模式,也类似于一些设计软件中的图层合层。但就上面的探照灯的效果,其又有点像是CSS的遮罩。其实在Canvas中,除了使用图像合成之外能实现上面的探照灯的效果之外,还可以使用Canvas的裁切clip()
方法。那么这一节,我们就来简单的了解Canvas的clip()
的相关知识点。
Canvas中的剪切
接下来我们要聊的不是图像的合成,而是Canvas中的另一个有用的功能:剪切区域。它是Canvas之中由路径所定义的一块区域,浏览器会将所有的绘图操作都限制在本区域内执行。在默认情况下,剪辑区域的大小与Canvas画布大小一致。除非你通过创建路径并调用Canvas绘图环境对象的clip()
方法来显式的设定剪辑区域,否则默认的剪辑区域不会影响Canvas之中所绘制的内容。然而,一旦设置好剪辑区域,那么你在Canvas之中绘制的所有内容都将局限在该区域内。这也意味着在剪辑区域以外进行绘制是没有任何效果的。
简单点讲,Canvas的裁切路径和普通的Canvas图形差不多,不同的是它的作用是遮罩,用来隐藏没有遮罩的部分。如下图所示,红边五角星就是裁切路径,所有在路径以外的部分都不会在Canvas上绘制出来。
从上图所示的效果来看,跟上一节介绍的globalCompositeOperation
中source-in
和source-atop
差不多的效果。最重要的区别是裁切路径不会在Canvas上绘制东西,而且它永远不受新图形的影响。这些特性使得它在特定区域里绘制图形时相当好用。
clip()使用
上面也说了,Canvas中的clip()
方法是裁切区可用于限制图像描绘的区域,具体的用法:
- 使用Canvas的绘制函数比如,
rect()
、arc()
之类的方法选择好绘图区域 - 使用
clip()
函数将该区域(由rect()
、arc()
方法指定的绘图区域)设定为裁选区
设定裁选区之后,无论在Canvas上绘制什么,只有落在裁选区内的那部分才能得以显示,其余都会被遮蔽掉。
先来看一个示例,绘制一个四个圆而且未使用裁选区:
ctx.save();
// 绘制第一个圆
ctx.beginPath();
ctx.fillStyle = 'tomato';
ctx.arc(cx, cy, radius, 0, Math.PI * 2, false);
ctx.fill();
// 绘制第二个圆
ctx.beginPath();
ctx.fillStyle = '#333';
ctx.arc(cx + 100, cy, radius, 0, Math.PI * 2, false);
ctx.fill();
// 绘制第三个圆
ctx.beginPath();
ctx.fillStyle = 'cornsilk';
ctx.arc(cx, cy + 100, radius, 0, Math.PI * 2, false);
ctx.fill();
// 绘制第四个圆
ctx.beginPath();
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 10;
ctx.arc(cx, cy, radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.restore();
看到的效果如下:
接下来我们分别来看看加上clip()
的效果,先来看看在第三个圆后面添加:
// 绘制第三个圆
ctx.beginPath();
ctx.fillStyle = 'cornsilk';
ctx.arc(cx, cy + 100, radius, 0, Math.PI * 2, false);
ctx.fill();
ctx.clip();
从效果图上,可以看出第四个圆(灰色的,只有边框的圆)只有部分绘制出来。接着往下,把clip()
往上移,放到第二个圆的后面:
// 绘制第二个圆
ctx.beginPath();
ctx.fillStyle = '#333';
ctx.arc(cx + 100, cy, radius, 0, Math.PI * 2, false);
ctx.fill();
ctx.clip();
原理同样的,第二个圆成为裁剪区域,第三个圆和第四个圆与第二个圆有交集的地方才会绘制出来,所以看到的效果如下:
继续按类似的方法操作,把clip()
放置在第一个圆的后面:
ctx.beginPath();
ctx.fillStyle = 'tomato';
ctx.arc(cx, cy, radius, 0, Math.PI * 2, false);
ctx.fill();
ctx.clip();
最终的效果,或许你已经可以猜得到了:
取消裁切区
当使用裁切区clip()
进行绘图后,可能需要取消该裁选区或者重新定义裁切区。在Canvas中,可以通过save()
函数和restore()
函数来实现——在构建裁切区之前保存状态,完成裁切区内的绘图之后进行状态读取。
同样拿上面的示例来举例,依旧在第三个圆后面做clip()
,并且同时做restore()
:
// 绘制第三个圆
ctx.beginPath();
ctx.fillStyle = 'cornsilk';
ctx.arc(cx, cy + 100, radius, 0, Math.PI * 2, false);
ctx.fill();
ctx.clip();
ctx.restore();
// 绘制第四个圆
ctx.beginPath();
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 10;
ctx.arc(cx, cy, radius, 0, Math.PI * 2, false);
ctx.stroke();
和前面的示例效果不一样,第四个灰色的边框圆,他并没有仅在第三个圆(裁切区)绘制,而是整个绘制出来了。接着往下看,把clip()
往上提,提到第二个圆之后,而restore()
位置不变:
如果把clip()
移动第一个圆之后,然后restore()
继续放置在第三个圆之后,看到的效果如下:
制作探照灯
通过前面的介绍,我们了解了:Canvas中的clip()
方法用于从原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。也可以在使用clip()
方法前通过使用save()
方法对当前画布区域进行保存,并在以后的任意时间通过restore()
方法对其进行恢复。
根据这个原理,我们就可以很轻松的实现下个探照灯的效果。
- 使用
arc()
绘制一个圆形的区域,然后在其后调用clip()
,设置剪切区域 - 使用
drawImage()
在画布中绘制一个图像 - 动态改变
arc()
的位置,从而看到一个类似探照灯的效果
具体的实现可以看下面的代码:
var canvas = document.getElementById('canvasOne');
var ctx = canvas.getContext('2d');
var w = canvas.width = window.innerWidth;
var h = canvas.height = window.innerHeight;
var image = new Image();
image.src = 'https://farm3.staticflickr.com/2908/32764885503_1a04915b11_k.jpg';
image.onload = function() {
ctx.drawImage(image, 0, 0);
}
// 设置探照灯对象模型
// @param (x, y): 表示圆心坐标
// @param radius: 圆心半径
// @param vx, vy: 水平和垂直方向的速度,通过他们控制速度大小
var spotlight = {
x: w / 2,
y: h / 2,
radius: 100,
vx: Math.random() * 5 + 10,
vy: Math.random() * 5 + 15
};
// 通过setInterval来更新模型的位置
// 每40ms更新一次
setInterval(function() {
draw();
update(w, h);
}, 40);
function draw() {
ctx.clearRect(0, 0, w, h);
ctx.save();
ctx.beginPath();
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, w, h);
ctx.fill();
ctx.save();
ctx.beginPath();
ctx.arc(spotlight.x, spotlight.y, spotlight.radius, 0, Math.PI * 2, false);
ctx.fill();
// 将上面的区域作为剪辑区域
ctx.clip();
// 由于使用clip(),画布背景图片会出现在clip()区域内
ctx.drawImage(image, 0, 0);
ctx.restore();
}
// 小球运动模型
function update(w, h) {
spotlight.x += spotlight.vx;
spotlight.y += spotlight.vy;
// 如果小球超出了左边的边界,则速度反向,x 点变为圆的半径
if (spotlight.x - spotlight.radius <= 0) {
spotlight.vx = -spotlight.vx;
spotlight.x = spotlight.radius;
}
// 如果小球超出了右边的边界,则速度反向,x点变为画布宽度-圆的半径
if (spotlight.x + spotlight.radius >= w) {
spotlight.vx = -spotlight.vx;
spotlight.x = w - spotlight.radius;
}
// Y轴方向处理
if (spotlight.y - spotlight.radius <= 0) {
spotlight.vy = -spotlight.vy;
spotlight.y = spotlight.radius;
}
if (spotlight.y + spotlight.radius >= h) {
spotlight.vy = -spotlight.vy;
spotlight.y = h - spotlight.radius;
}
}
最后效果如下:
这个探照灯的效果是使用clip()
方法来实现的,如果你感兴趣的话,可以把这个效果的实现原理与前面提供的示例(图像合成制作探照灯效果)对比,看看这两者制作方案有何不同之处。
总结
这篇文章主要介绍了Canvas中的clip()
方法的特性。clip()
方法将剪切区域设置为当前剪切区域与当前路径的交集。在第一次调用clip()
方法之前,剪切区域与整个Canvas画布大小一致。因为clip()
方法会将剪切区域设置为当前剪切区域与当前路径的交集,所以对该方法的调用一般都是嵌入save()
和restore()
方法之间的。否则,剪切区域将会越变越小,这通常不是我们想要的效果。合理的运用好clip()
和save()
以及restore()
方法我们就可制作出不同的效果,比如文章中介绍的探照灯的效果。
如需转载,烦请注明出处:http://www.w3cplus.com/canvas/clip.html