我遇到的问题非常简单。这是"我如何画一个形状的孔?"问题,经典答案是"简单地在同一条路径中绘制两个形状,但顺时针绘制实心和"孔"逆时针&#34。这很棒,但是"洞"我需要的是复合形状,由多个圆圈组成。
视觉描述:http://i.imgur.com/9SuMSWT.png。
jsfiddle:http://jsfiddle.net/d_panayotov/44d7qekw/1/
context = document.getElementsByTagName('canvas')[0].getContext('2d');
// green background
context.fillStyle = "#00FF00";
context.fillRect(0,0,context.canvas.width, context.canvas.height);
context.fillStyle = "#000000";
context.globalAlpha = 0.5;
//rectangle
context.beginPath();
context.moveTo(0, 0);
context.lineTo(context.canvas.width, 0);
context.lineTo(context.canvas.width, context.canvas.height);
context.lineTo(0, context.canvas.height);
//first circle
context.moveTo(context.canvas.width / 2 + 20, context.canvas.height / 2);
context.arc(context.canvas.width / 2 + 20, context.canvas.height / 2, 50, 0, Math.PI*2, true);
//second circle
context.moveTo(context.canvas.width / 2 - 20, context.canvas.height / 2);
context.arc(context.canvas.width / 2 - 20, context.canvas.height / 2, 50, 0, Math.PI*2, true);
context.closePath();
context.fill();
修改
已提出多种解决方案,我觉得我的问题一直存在误导。所以这里有更多信息: 我需要矩形区域作为阴影。这是我制作的游戏的截图(希望这不违反规则):http://i.imgur.com/tJRjMXC.png。
@markE:
@hobberwickey: 这是一个静态背景,而不是实际的画布内容。然而,我可以使用clip(),就像我使用的那样#34; source-atop"但那效率很低。
我现在实施的解决方案:http://jsfiddle.net/d_panayotov/ewdyfnj5/。我只是在主画布内容上绘制剪切的矩形(在内存中的画布中)。有没有更快/更好的解决方案?
答案 0 :(得分:2)
我几乎不敢发布这个答案的第一部分,因为它很简单,但为什么不在实心背景上填充2个圆圈呢?
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var r=50;
ctx.fillStyle='rgb(0,174,239)';
ctx.fillRect(0,0,cw,ch);
ctx.fillStyle='white'
ctx.beginPath();
ctx.arc(cw/2-r/2,ch/2,r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.arc(cw/2+r/2,ch/2,r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=400 height=168></canvas>
或者......“敲掉”(擦除)双圈......
如果你想让2个圆圈“击倒”蓝色像素,那么双圆圈是透明的&amp;揭示下面的网页背景,然后你可以使用合成来“淘汰”圈子:context.globalCompositeOperation='destination-out
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var r=50;
// draw the blue background
// The background will be visible only outside the double-circles
ctx.fillStyle='rgb(0,174,239)';
ctx.fillRect(0,0,cw,ch);
// use destination-out compositing to "knockout"
// the double-circles and thereby revealing the
// ivory webpage background below
ctx.globalCompositeOperation='destination-out';
// draw the double-circles
// and effectively "erase" the blue background
ctx.fillStyle='white'
ctx.beginPath();
ctx.arc(cw/2-r/2,ch/2,r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.arc(cw/2+r/2,ch/2,r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
// always clean up! Set compositing back to its default
ctx.globalCompositeOperation='source-over';
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=400 height=168></canvas>
另一方面......
如果您需要将这些双圆圈像素隔离为包含路径,则可以使用合成绘制到双圆圈而不绘制到蓝色背景中。
这是另一个例子:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var r=50;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/mm.jpg";
function start(){
// fill the double-circles with any color
ctx.fillStyle='white'
ctx.beginPath();
ctx.arc(cw/2-r/2,ch/2,r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.arc(cw/2+r/2,ch/2,r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
// set compositing to source-atop
// New drawings are only drawn where they
// overlap existing (non-transparent) pixels
ctx.globalCompositeOperation='source-atop';
// draw your new content
// The new content will be visible only inside the double-circles
ctx.drawImage(img,0,0);
// set compositing to destination-over
// New drawings will be drawn "behind"
// existing (non-transparent) pixels
ctx.globalCompositeOperation='destination-over';
// draw the blue background
// The background will be visible only outside the double-circles
ctx.fillStyle='rgb(0,174,239)';
ctx.fillRect(0,0,cw,ch);
// always clean up! Set compositing back to its default
ctx.globalCompositeOperation='source-over';
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=400 height=168></canvas>
{除了回答之外的其他想法}
技术要点: xor
合成只需在像素上翻转alpha值,但也不会将像素的r,g,b部分清零。在某些情况下,xored像素的alphas将被取消归零,rgb将再次显示。最好使用'destination-out'合成,其中像素值(r,g,b,a)的所有部分都被清零,这样它们就不会意外地返回困扰你。
确保... 尽管在您的示例中并不重要,但您应始终使用maskCtx.beginPath()
开始路径绘制命令。这表示任何先前绘图的结束和新路径的开始。
一个选项:我看到你正在使用同心圆在你圈子的中心引起更大的“揭示”。如果你想要一个更渐进的显示,那么你可以用剪切阴影(或径向渐变)而不是同心圆来淘汰你的记忆中的圆圈。
除此之外,覆盖内存中画布的解决方案应该运行良好(以内存中画布使用的内存为代价)。
祝你好运!
答案 1 :(得分:0)
更简单的方法就是使用剪辑和完整的圆圈。除非出于某种原因,你需要用一条路径来做这件事。
var cutoutCircle = function(x, y, r, ctx){
ctx.save()
ctx.arc(x, y, r, 0, Math.PI * 2, false)
ctx.clip()
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
ctx.restore();
}
var myCircles = [{x: 75, y: 100, r: 50}, {x: 125, y: 100, r: 50}],
ctx = document.getElementById("canvas").getContext('2d');
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (var i=0; i<myCircles.length; i++){
cutoutCircle(myCircles[i].x, myCircles[i].y, myCircles[i].r, ctx)
}
编辑:为了更好的演示而添加背景示例
答案 2 :(得分:0)
如果我理解正确的话:你想在游戏顶部看到一个面具的外观,这样两个相交的圆圈突出显示,而其他一切都变暗了?
我建议保持简单 - 创建一个离屏画布,在透明的黑色背景上敲出圆圈。
然后在需要时在游戏顶部绘制离屏画布。这比为每个帧重新组合要高得多 - 做一次并重复使用。
掩码显示在下面的演示窗口中(滚动它或使用整页查看全部)。通常你会创建一个离屏画布并使用它。
// create mask
// for off-screen, use createElement("canvas")
var mask = document.getElementById("mask"),
ctxm = mask.getContext("2d"),
w = mask.width, h = mask.height, x, y, radius = 80;
ctxm.fillStyle = "rgba(0,0,0,0.5)";
ctxm.fillRect(0, 0, w, h); // fill mask with 50% transp. black
ctxm.globalCompositeOperation = "destination-out"; // knocks out background
ctxm.fillStyle = "#000"; // some solid color
x = w / 2 - radius/1.67;
y = h / 2;
ctxm.moveTo(x, y); // circle 1
ctxm.arc(x, y, radius, 0, Math.PI*2);
x = w / 2 + radius/1.67;
ctxm.moveTo(x, y); // circle 2
ctxm.arc(x, y, radius, 0, Math.PI*2);
ctxm.fill(); // knock em' out, DONE!
// ----- Use mask for the game, pseudo action below ------
var canvas = document.getElementById("game"), ctx = canvas.getContext("2d");
(function loop() {
ctx.fillStyle = "#742";
ctx.fillRect(0, 0, w, h); // clear background
ctx.fillStyle = "#960";
for(x = 0; x < w; x += 8) // some random action
ctx.fillRect(x, h * Math.random(), 8, 8);
ctx.drawImage(mask, 0, 0); // use MASK on top
requestAnimationFrame(loop)
})();
<canvas id="mask" width=500 height=220></canvas>
<canvas id="game" width=500 height=220></canvas>